# Live Artifacts via Agent Skills

**Status:** Draft · 2026-04-29  
**Parent:** [`docs/spec.md`](../../docs/spec.md)  
**Siblings:** [`docs/skills-protocol.md`](../../docs/skills-protocol.md) · [`docs/agent-adapters.md`](../../docs/agent-adapters.md) · [`docs/modes.md`](../../docs/modes.md)  
**Reference implementation:** `~/Projects/monet` connectors + live artifacts

This spec defines how to bring Monet's **connectors** and **live artifacts** ideas into Open Design, but implement the agent-facing surface as **file-based agent skills plus daemon-owned local tools**, not as an in-process tool registry or MCP-first integration.

---

## 1. Product goal

Open Design should let an agent create previewable artifacts that are not just one-off generated files, but **live, refreshable, auditable views** backed by external or local data sources.

Examples:

- “Create a live GitHub release dashboard.”
- “Make a Notion project status page and let me refresh it tomorrow.”
- “Turn this folder of JSON files into a polished stakeholder report.”
- “Create a design-system coverage artifact that can be refreshed after code changes.”

The user experience should feel like the existing OD artifact flow:

1. User chats with the selected agent.
2. Agent uses a skill to plan and create a live artifact.
3. OD persists the artifact as project-scoped files and metadata.
4. UI previews the artifact in the existing iframe/file viewer model.
5. User can refresh the artifact later without asking the agent to redesign it from scratch.

## 2. Key decision

### 2.1 Use `skill + daemon tool endpoint`, not MCP-first

Monet exposes connectors and live artifacts through a controller-owned tool registry. OD should not copy that exact runtime shape because OD's core architecture is different: OD delegates to external CLI agents such as Claude Code, Codex, Cursor Agent, Gemini CLI, OpenCode, and Qwen.

The agent-facing interface should therefore be:

```text
skills/live-artifact/SKILL.md
  ↓ instructs the agent to call
daemon local HTTP endpoints or wrapper CLI commands
  ↓ backed by
daemon-owned connector + artifact services
  ↓ persisted as
project workspace files + metadata
```

MCP may be added later as a wrapper over the same daemon services, but it should not be the first or only interface.

Reasons:

- **Multi-agent compatibility:** every supported agent can read a skill and execute shell commands; MCP support varies by agent and CLI version.
- **Lower migration cost:** current daemon `/api/chat` does not support per-run MCP binding.
- **Centralized safety:** daemon endpoints can enforce project, path, connector, and output-size policies consistently.
- **Skill-native product model:** OD's extension point is already `skills/` + `SKILL.md`, so live artifacts should feel like another OD capability, not a separate agent protocol.

### 2.2 Keep live artifacts distinct, but project-native

Live artifacts are a distinct persisted model integrated into the existing project UI. They must not be represented as a new static `ArtifactKind` in the existing artifact model, because they require ID-based identity, directory-shaped runtime storage, refresh/provenance history, connector permissions, locking, and server-rendered preview behavior.

Product terms:

- **Design / project:** the workspace container.
- **Artifact:** a static generated file inside a design.
- **Live artifact:** a refreshable, data-backed artifact inside a design.
- **Connector:** an external or local data source available to live artifacts.

Implementation boundaries:

- Keep dedicated live-artifact storage under `.live-artifacts/`, dedicated `/api/live-artifacts/*` endpoints, and dedicated live-artifact DTOs in `packages/contracts`.
- Reuse the existing project scope, workspace tabs, file tree, viewer primitives, chat SSE stream, and API error envelope so live artifacts feel native without polluting the simple static artifact path.
- Do not expose `.live-artifacts/` through generic project file APIs; all mutation should go through live-artifact or tool endpoints.

## 3. What to migrate from Monet

### 3.1 Concepts to preserve

From `~/Projects/monet`:

- Static connector catalog plus dynamic connection status.
- Connector tool safety classification.
- Read-only-first connector policy.
- Live artifact / tile / source / provenance separation.
- HTML document template plus data-binding contract.
- Declarative output mapping from tool output to `data.json` / render models.
- Strict render JSON validation.
- Refresh source validation before re-execution.
- Refresh audit trail with step-level status.
- Failure fallback: invalid refresh output should not blank the artifact.

### 3.2 Concepts to adapt

Monet concept | OD adaptation
---|---
Controller `ToolRegistry` | Daemon service endpoints and optional CLI wrappers
Chat tools `create_live_artifact`, `update_live_artifact`, `list_live_artifacts` | Skill instructions that call `od-tools live-artifacts ...` or localhost daemon endpoints
Connector tools dynamically injected into tool registry | Connector catalog exposed through daemon endpoints; skill asks agent to query/use them explicitly
SQLite-first artifact storage | Project-scoped metadata files first; SQLite optional later if indexing becomes necessary
Controller-owned agent loop | External CLI agent loop; OD only injects skills and receives output/events

### 3.3 Monet files used as source material

- `apps/controller/src/connectors/catalog.ts`
- `apps/controller/src/connectors/service.ts`
- `apps/controller/src/routes/connectors.ts`
- `apps/controller/src/tools/connectors.ts`
- `apps/controller/src/live-artifacts/schema.ts`
- `apps/controller/src/live-artifacts/render.ts`
- `apps/controller/src/live-artifacts/refresh.ts`
- `apps/controller/src/routes/live-artifacts.ts`
- `apps/controller/src/tools/live-artifacts.ts`
- `apps/controller/src/chat-storage.ts`
- `specs/2026-04-27-live-artifacts/spec.md`

## 4. Target architecture

```text
┌──────────────────────────────────────────────────────────────────┐
│ Web App                                                          │
│ chat · artifact tree · live artifact list · refresh button        │
│ iframe preview · source/provenance panels                         │
└───────────────┬──────────────────────────────────────────────────┘
                │ HTTP/SSE
┌───────────────▼──────────────────────────────────────────────────┐
│ Local Daemon                                                      │
│                                                                  │
│  Agent session broker                                             │
│  Skill registry                                                   │
│  Built-in tool endpoints                                          │
│    /api/tools/live-artifacts/*                                    │
│    /api/tools/connectors/*                                        │
│    /api/connectors/*                                              │
│  Artifact store                                                   │
│  Connector service                                                │
│  Refresh runner + audit log                                       │
└───────────────┬──────────────────────────────────────────────────┘
                │ spawn / stdio
┌───────────────▼──────────────────────────────────────────────────┐
│ External Agent CLI                                                │
│ Claude Code · Codex · Cursor Agent · Gemini CLI · OpenCode · Qwen │
│                                                                  │
│ Receives SKILL.md instructions and calls daemon tools via shell   │
└──────────────────────────────────────────────────────────────────┘
```

## 5. User-facing skill shape

Add a built-in skill:

```text
skills/live-artifact/
├── SKILL.md
├── references/
│   ├── artifact-schema.md
│   ├── connector-policy.md
│   └── refresh-contract.md
└── assets/
    └── templates/
        ├── dashboard.html
        └── report.html
```

### 5.1 `SKILL.md` frontmatter

```yaml
---
name: live-artifact
description: |
  Create refreshable, auditable Open Design artifacts backed by connector or local data.
  Trigger when the user asks for live dashboards, refreshable reports, synced views, or reusable data-backed artifacts.
triggers:
  - live artifact
  - refreshable dashboard
  - live report
  - synced view
  - 可刷新
  - 实时看板
od:
  mode: prototype
  preview:
    type: html
    entry: index.html
    reload: debounce-100
  design_system:
    requires: true
  outputs:
    primary: index.html
    secondary:
      - template.html
      - artifact.json
      - data.json
      - provenance.json
  capabilities_required:
    - shell
    - file_write
---
```

### 5.2 Skill body responsibilities

The skill should instruct the agent to:

1. Determine whether the user wants a live artifact or a normal static artifact.
2. Query available connectors and allowed read-only operations.
3. Fetch from the named connected connector/source when available; ask for a data source only when no matching connected source exists, multiple candidates are equally plausible, or the request lacks any searchable topic/page/database clue.
4. Create a safe render model, not raw provider output.
5. Write `template.html`, `data.json`, `artifact.json`, and `provenance.json` into the live artifact workspace directory; treat `index.html` as derived preview output.
6. Register the artifact through daemon tooling.
7. Include provenance and refresh source metadata.
8. Never store credentials, raw OAuth responses, headers, cookies, or tokens.

### 5.3 Agent-callable command surface

Prefer a small `od` wrapper command over raw `curl` in the skill body:

```bash
od tools live-artifacts create --input artifact.json
od tools live-artifacts list --format compact
od tools live-artifacts update --artifact-id "$ID" --input artifact.json
od tools live-artifacts refresh --artifact-id "$ID"
od tools connectors list --format compact
od tools connectors execute --connector github --tool list_releases --input input.json
```

The wrapper should be implemented as TypeScript source under `apps/daemon/src` and call daemon endpoints using injected runtime values:

- `OD_DAEMON_URL`
- `OD_TOOL_TOKEN`

The daemon injects these into the system prompt or skill preamble at runtime. The agent should not choose or override `projectId`; `/api/tools/*` derives project/run scope from `OD_TOOL_TOKEN`. If standalone JavaScript wrappers are later exposed, they must be generated build output from TypeScript source, not project-owned `.js` source files.

Raw HTTP is for developer debugging only and must include the run-scoped bearer token:

```bash
curl -s -X POST "$OD_DAEMON_URL/api/tools/live-artifacts/create" \
  -H 'content-type: application/json' \
  -H "authorization: Bearer $OD_TOOL_TOKEN" \
  -d @artifact.json
```

## 6. Daemon API design

### 6.1 Connector endpoints

```http
GET    /api/connectors
GET    /api/connectors/:connectorId
POST   /api/connectors/:connectorId/connect
DELETE /api/connectors/:connectorId/connection
```

MVP may stub OAuth-backed connectors and start with local/read-only connectors, but the API should preserve Monet's split between catalog and connection status. OAuth callback routes are deferred until OAuth-backed connectors are implemented.

Connector response shape:

```ts
type ConnectorDetail = {
  id: string;
  label: string;
  category: 'code' | 'docs' | 'files' | 'analytics' | 'custom';
  status: 'available' | 'connected' | 'error' | 'disabled';
  accountLabel?: string;
  featuredTools: ConnectorToolSummary[];
  allowedTools: ConnectorToolSummary[];
  minimumApprovalPolicy: 'read_only_auto' | 'confirm_write' | 'disabled';
  errorCode?: string;
};
```

### 6.2 Connector tool endpoints

Agent and refresh-runner connector execution must use the same daemon-owned execution path:

```http
GET  /api/tools/connectors/list
POST /api/tools/connectors/execute
```

`/api/tools/connectors/list` returns a compact list of connected, allowed, read-only-first tools for the current run token.

`/api/tools/connectors/execute` request:

```ts
type ConnectorExecuteRequest = {
  connectorId: string;
  toolName: string;
  input: BoundedJsonObject;
  purpose: 'agent_preview' | 'artifact_refresh';
};
```

Response:

```ts
type ConnectorExecuteResponse =
  | {
      ok: true;
      connectorId: string;
      accountLabel?: string;
      toolName: string;
      safety: ConnectorToolSafety;
      output: BoundedJsonValue;
      outputSummary?: string;
      providerExecutionId?: string;
      metadata?: BoundedJsonObject;
    }
  | ApiErrorResponse;
```

Execution rules:

- Require a valid `OD_TOOL_TOKEN` bound to the active run/project.
- Reject tools that are not in the connector catalog allowlist.
- Re-classify tool safety at execution time; catalog metadata alone is not authorization.
- Reject `write`, `destructive`, and `unknown` tools for `artifact_refresh`.
- Bound output size before it is returned to the agent.
- Redact credentials and raw provider envelope fields before returning or persisting anything.
- Record `providerExecutionId`, connector/account labels, and safety policy for provenance.

### 6.3 Live artifact endpoints

Agent/tool endpoints:

```http
POST /api/tools/live-artifacts/create
GET  /api/tools/live-artifacts/list
POST /api/tools/live-artifacts/update
POST /api/tools/live-artifacts/refresh
```

UI endpoints:

```http
GET   /api/live-artifacts?projectId=...
POST  /api/live-artifacts
GET   /api/live-artifacts/:artifactId
PATCH /api/live-artifacts/:artifactId
POST  /api/live-artifacts/:artifactId/refresh
GET   /api/live-artifacts/:artifactId/preview
```

The `/api/tools/*` endpoints are optimized for agent consumption: compact JSON, concise errors, and explicit machine-readable validation failures. They never accept an arbitrary `projectId`; project/run scope comes from `OD_TOOL_TOKEN`. The `/api/live-artifacts/*` endpoints are optimized for UI state and use the web app's normal project context.

Both endpoint families must call the same service-layer validation and storage code. Only authentication and response verbosity should differ; errors should share the `ApiErrorResponse` envelope from `packages/contracts`.

Agent-facing tool endpoints should reuse the shared API error envelope from `packages/contracts/src/errors.ts` instead of introducing a parallel error type:

```ts
type LiveArtifactToolResponse<TSuccess> = TSuccess | ApiErrorResponse;
```

Add live-artifact and connector-specific `ApiErrorCode` values such as `TOOL_TOKEN_INVALID`, `TOOL_TOKEN_EXPIRED`, `CONNECTOR_NOT_CONNECTED`, `CONNECTOR_SAFETY_DENIED`, `REFRESH_LOCKED`, `REFRESH_TIMED_OUT`, `OUTPUT_TOO_LARGE`, `TEMPLATE_BINDING_INVALID`, and `REDACTION_REQUIRED`. Validation details should live in the existing error `details` field so web, daemon, and tests share one error model.

## 7. Data model

### 7.1 Storage layout

Use project-scoped files under the daemon runtime data directory first. `OD_DATA_DIR` may override the default; otherwise `<RUNTIME_DATA_DIR>` is `<repo>/.od`:

```text
<RUNTIME_DATA_DIR>/projects/<projectId>/
└── .live-artifacts/
    └── <artifactId>/
        ├── artifact.json
        ├── template.html
        ├── index.html
        ├── data.json
        ├── provenance.json
        ├── refreshes.jsonl
        └── snapshots/
            └── <refreshId>/
                ├── data.json
                └── provenance.json
```

The dot-prefixed `.live-artifacts/` directory keeps implementation files out of the generic project file tree while preserving OD's file-first, inspectable-on-disk artifact philosophy. Add SQLite later only for cross-project indexing or high-volume refresh history.

`index.html` is a generated preview artifact, not the source of truth for refreshable data. The UI should load live artifacts through:

```http
GET /api/live-artifacts/:artifactId/preview
```

The preview route may serve the stored `index.html` for static cases, but for refreshable HTML it should render `template.html + data.json` and apply iframe sandbox/CSP headers. `snapshots/` should be hidden from the normal artifact tree unless the user explicitly opens refresh history.

### 7.2 Core types

```ts
type BoundedJsonValue =
  | null
  | boolean
  | number
  | string
  | BoundedJsonValue[]
  | { [key: string]: BoundedJsonValue };

type BoundedJsonObject = { [key: string]: BoundedJsonValue };

type LiveArtifact = {
  schemaVersion: 1;
  id: string;
  projectId: string;
  sessionId?: string;
  createdByRunId?: string;
  title: string;
  slug: string;
  status: 'active' | 'archived' | 'error';
  pinned: boolean;
  preview: {
    type: 'html' | 'jsx' | 'markdown';
    entry: string;
  };
  refreshStatus: 'never' | 'idle' | 'running' | 'succeeded' | 'failed';
  createdAt: string;
  updatedAt: string;
  lastRefreshedAt?: string;
  document?: LiveArtifactDocument;
};

type LiveArtifactDocument = {
  format: 'html_template_v1';
  templatePath: 'template.html';
  generatedPreviewPath: 'index.html';
  dataPath: 'data.json';
  dataJson: BoundedJsonObject;
  dataSchemaJson?: BoundedJsonObject;
  sourceJson?: LiveArtifactSource;
};

type LiveArtifactSource = {
  type: 'local_file' | 'daemon_tool' | 'connector_tool';
  toolName?: string;
  input: BoundedJsonObject;
  connector?: {
    connectorId: string;
    accountLabel?: string;
    toolName: string;
    approvalPolicy: 'read_only_auto' | 'manual_refresh_granted_for_read_only';
  };
  outputMapping?: {
    dataPaths?: Array<{ from: string; to: string }>;
    transform?: 'identity' | 'compact_table' | 'metric_summary';
  };
  refreshPermission: 'none' | 'manual_refresh_granted_for_read_only';
};

type LiveArtifactProvenance = {
  generatedAt: string;
  generatedBy: 'agent' | 'refresh_runner';
  notes?: string;
  sources: Array<{
    label: string;
    type: 'connector' | 'local_file' | 'user_input' | 'derived';
    ref?: string;
  }>;
};
```

### 7.3 Validation rules

Port Monet's strict validation posture:

- Apply the shared bounded JSON constraints in `packages/contracts` to every persisted or agent-supplied `BoundedJsonValue` / `BoundedJsonObject`.
- Reject keys such as `raw`, `rawResponse`, `payload`, `body`, `headers`, `cookie`, `authorization`, `token`, `secret`, `credential`, `password`.
- Redact suspicious source inputs before persistence.
- Reject source inputs that still contain credential-like values after redaction.
- HTML preview files must be generated from the document contract; refresh updates `data.json`, not arbitrary script.

#### 7.3.1 Shared bounded JSON constraints

The shared live-artifact JSON envelope is intentionally small enough to validate synchronously, store in project files, display in the UI, and include in agent-facing error details without leaking raw provider payloads.

Define and export these constants from `packages/contracts` as `LIVE_ARTIFACT_BOUNDED_JSON_CONSTRAINTS`:

| Constraint | Value | Applies to |
| --- | ---: | --- |
| Maximum object/array depth | `8` | Any `BoundedJsonValue`; count the root object or array as depth `1`. |
| Maximum object keys | `100` | Any single object. |
| Maximum array length | `500` | Any single array. |
| Maximum string length | `16 KiB` | Any single string value, measured in UTF-16 code units before persistence. |
| Maximum serialized payload size | `256 KiB` | Any complete bounded JSON document, measured as UTF-8 bytes of canonical `JSON.stringify` output. |

Validation must fail closed when a value exceeds any limit. Persisted files and create/update inputs must use the same limits so valid agent input remains valid after storage round-trips. Future connector-specific limits may be stricter, but must not exceed this shared envelope for values persisted into live artifact files.

### 7.4 HTML document model

MVP live HTML artifacts should use `html_template_v1`:

```text
template.html + data.json → daemon render step → index.html / preview response
```

Rules:

- `template.html` is authored by the agent during create/update.
- Refreshable values must come from `data.json`, not be hardcoded only in HTML.
- `html_template_v1` supports **Mustache-style escaped interpolation plus a minimal `data-od-repeat` structural directive**. It does not support arbitrary JavaScript, expression evaluation, helper functions, filters, partials, or raw HTML injection.
- Refresh updates `data.json` and snapshots. It does not let connector output rewrite arbitrary HTML.
- If a presentation redesign is needed, the user should ask the agent to update the artifact; refresh is for data changes.
- `index.html` may be regenerated after successful refresh, but it is derived output.
- The preview route must serve the document in a sandboxed iframe context with a restrictive CSP. External scripts are disallowed in MVP unless vendored and allowlisted.

#### 7.4.1 `html_template_v1` binding contract

MVP binding syntax is intentionally small and deterministic:

- **Escaped interpolation:** `{{data.path.to.value}}` inserts a scalar value from `data.json`.
  - Paths must start with `data` and use dot-separated object keys, e.g. `{{data.summary.title}}`.
  - Numeric array indexes are allowed only as path segments, e.g. `{{data.metrics.0.value}}`.
  - Keys must match `/^[A-Za-z_][A-Za-z0-9_-]*$/`; bracket notation, computed paths, wildcards, function calls, and expressions are invalid.
  - Values render as strings. `null` and missing values render as an empty string. Objects and arrays are invalid interpolation targets except inside a supported repeat context.
- **Repeat directive:** `data-od-repeat="item in data.items"` repeats the annotated element once for each object in an array.
  - The left side is a local alias matching `/^[A-Za-z_][A-Za-z0-9_]*$/`.
  - The right side must be a `data.*` path resolving to an array.
  - Inside the repeated element, interpolation may reference the local alias, e.g. `{{item.name}}`, using the same path grammar.
  - Nested `data-od-repeat` directives are disallowed in MVP.
  - `data-od-repeat` is removed from the generated output.
- **Conditional directives:** none in MVP. Optional sections should be represented by empty strings, zero-length arrays, or separate agent-authored template variants during update.
- **Attribute bindings:** interpolation may appear in text nodes and ordinary HTML attribute values, but not in attribute names, tag names, comments, `<script>`, `<style>`, `<iframe srcdoc>`, event-handler attributes such as `onclick`, or URL-bearing attributes that fail URL validation.

All interpolation is HTML-escaped by default:

- Text-node interpolation escapes `&`, `<`, `>`, `"`, and `'`.
- Attribute interpolation escapes the same characters and must not be allowed to break out of the containing attribute.
- URL-bearing attributes such as `href` and `src` must resolve to allowed `http:` or `https:` URLs, root-relative paths, same-artifact relative asset paths, or fragments; `javascript:`, `data:`, `blob:`, and other unsupported schemes are rejected.

Raw / unescaped interpolation is explicitly forbidden in MVP:

- No triple braces such as `{{{data.html}}}`.
- No ampersand form such as `{{& data.html}}`.
- No `data-od-html`, `data-od-raw`, `data-od-bind-html`, or equivalent raw insertion attributes.
- No opt-out flag in artifact metadata or tool input.

The daemon must validate `template.html` before persistence and again before preview rendering. Validation failures must use the shared API error envelope with field/path details in `details`.

## 8. Runtime flows

### 8.1 Create flow

```text
User asks for a live dashboard
  ↓
OD selects/injects live-artifact skill
  ↓
Agent queries connectors or local sources through daemon wrapper
  ↓
Agent writes template.html + artifact.json + data.json + provenance.json
  ↓
Agent calls live-artifacts create endpoint
  ↓
Daemon validates schemas, source metadata, file paths, and template binding
  ↓
Daemon stores artifact metadata and returns compact summary
  ↓
Web UI opens /api/live-artifacts/:artifactId/preview
```

### 8.2 Refresh flow

```text
User clicks Refresh
  ↓
UI POST /api/live-artifacts/:id/refresh
  ↓
Daemon loads artifact.json and the refreshable document source
  ↓
For the document source:
  - verify refreshPermission
  - verify connector still connected
  - verify tool is still read-only
  - verify accountLabel/connectorId did not drift
  - verify saved input matches current schema
  - execute source
  - map output through declarative outputMapping.dataPaths
  - update candidate dataJson
  - validate sanitized data
  ↓
Write refresh step to refreshes.jsonl
  ↓
If the refreshable source succeeds, commit data.json, snapshot, and regenerated preview
If it fails, keep previous data/preview, write failed refresh record, and surface the error
```

MVP refresh is **artifact-level all-or-nothing**.

Refresh runner requirements:

- Acquire a per-artifact refresh lock. Reject or queue concurrent refreshes.
- Assign a monotonic `refreshId`; stale refreshes cannot overwrite newer committed data.
- Enforce per-source and total refresh timeouts.
- Persist every step to `refreshes.jsonl` with status, duration, connector metadata, and compact error.
- On daemon restart, recover refreshes stuck in `running` past timeout as `failed` and keep the last valid preview.
- Write snapshots only after validation succeeds, or write failed attempts under a separate `snapshots/<refreshId>/failed/` directory that is not used for preview.

### 8.3 Update flow

Agent or UI can update title, pinned status, archive status, preview entry, or non-source presentation fields. Updating `sourceJson` requires the same validation as create.

## 9. Connector strategy

### 9.1 Read-only v1

MVP should only expose read-only connector tools to automatic or refresh execution.

Write actions can exist later, but they must require explicit user confirmation and should not be refreshable.

Safety classification:

```ts
type ConnectorToolSafety = {
  sideEffect: 'read' | 'write' | 'destructive' | 'unknown';
  approval: 'auto' | 'confirm' | 'disabled';
  reason: string;
};
```

Rules:

- OAuth scopes or names containing `write`, `create`, `update`, `delete`, `admin`, `send`, `post`, `manage` imply write/confirm.
- Destructive hints imply destructive/disabled for refresh.
- Explicit read-only hints can be read/auto.
- Unknown defaults to write/confirm, not read/auto.

### 9.2 Execute-time enforcement

Connector policy must be enforced at execution and refresh time, not only when the catalog is built:

- Catalog classification is metadata, not authorization.
- `/api/tools/connectors/execute` re-checks allowlist, current scopes, tool safety, and connector status.
- Saved artifact policy cannot grant new permission by itself.
- `unknown`, `write`, and `destructive` tools are never refreshable.
- If a previously read-only tool later appears write-capable because scopes or provider metadata changed, refresh must fail closed.
- A write action may be supported in the future with explicit confirmation, but it must not be stored as a refreshable source.
- Agent calls and refresh-runner calls must share the same connector execution service so audit and safety behavior cannot drift.

### 9.3 Credential storage

Default decision:

- OAuth connection state and credentials live outside project artifacts, under a daemon-controlled global store such as `~/.open-design/connectors/` or an app database.
- Project artifacts only store stable references: `connectorId`, `accountLabel`, provider tool id/name, minimized input, and provenance.
- Access tokens, refresh tokens, headers, cookies, OAuth state, and raw provider responses are never written under `<RUNTIME_DATA_DIR>/projects/<projectId>/.live-artifacts` or any other project artifact directory.
- Refresh resolves credentials through the daemon connector service at execution time.
- UI must show the connector/account label so users understand which global connection backs a project artifact.

### 9.4 Initial connector candidates

MVP can avoid OAuth complexity by shipping local daemon tools first:

- `project_files.search`
- `project_files.read_json`
- `git.summary`
- `github.public_repo_summary` using unauthenticated public API or user-provided token later

OAuth-backed providers can follow the Monet pattern after the artifact pipeline is stable:

- GitHub
- Notion
- Google Drive
- Linear

## 10. UI changes

### 10.1 Entry header connector tab

Add a `Connectors` tab to the entry-header navigation. When selected, it should show cards for available external and local connectors.

Connector cards should include, as data becomes available:

- connector name and provider/category
- connection status
- connected account label, when connected
- connect, disconnect, or configure action
- available read-only tools/capabilities, when useful

This tab is a workspace-level connector management surface, not a separate project type. Phase 1B may show stubbed or local-only connector card data; full external connector status, connect/disconnect actions, and OAuth-backed flows belong to the connector phase.

### 10.2 New project live artifact entry

Add `New live artifact` to the new project tabs. Selecting it should start the normal project creation flow with live-artifact intent and the `live-artifact` skill path preselected or strongly hinted.

This creates a normal project/design whose first output is expected to be a live artifact. It should not create a separate top-level project type unless the product model changes later.

Live artifacts should also be listed in the existing `Designs` tab. The `Designs` tab should continue to show normal designs/projects, and it should additionally surface live artifacts as first-class selectable entries associated with their parent project/design. Live artifact entries should be visually distinguishable with `Live` / `Refreshable` status and should open directly into the project workspace with the corresponding live artifact selected. Parent design cards may also show a small live-artifact count or status summary.

### 10.3 Artifact tree

Show live artifacts as a first-class virtual group in the existing artifact tree, not as raw nested files. The tree should show one node per live artifact and hide implementation files such as `snapshots/` by default.

`ProjectView.tsx` should fetch `GET /api/live-artifacts?projectId=...` alongside the existing project file fetch, then merge live artifacts into the workspace state as a discriminated item such as `kind: 'live-artifact'`. Use namespaced tab IDs such as `live:<artifactId>` so live artifacts cannot collide with file paths. `FileWorkspace.tsx` should render these items in a virtual group and route open/select actions to a live artifact viewer without treating artifact IDs as normal project file paths.

Badges:

- `Live`
- `Refreshable`
- `Refreshing...`
- `Refresh failed`
- `Archived`

### 10.4 Preview panel

Reuse existing iframe/file viewer where possible:

- Load `GET /api/live-artifacts/:artifactId/preview` in the iframe instead of opening nested files directly.
- Add read-only viewer toolbar tabs using the existing `FileViewer.tsx` / `HtmlViewer` tab pattern: `Preview`, `Source`, `Data`, `Provenance`, `Refresh history`. Do not require a new split-pane side panel in Phase 1B.
- Show refresh button only when at least one tile is refreshable. When `refreshStatus` is `running`, the button should be disabled and show a loading state to prevent duplicate refreshes.

Data sources for viewer tabs:

- `Source`: `artifact.json` `sourceJson` fields with credentials redacted.
- `Data`: current `data.json` and tile render summaries.
- `Provenance`: `provenance.json` and connector/account labels.
- `Refresh history`: parsed `refreshes.jsonl`, newest first.

### 10.5 Chat integration

When an agent creates or updates a live artifact, the daemon should emit an agent/UI event similar to produced files so the UI can open it automatically. For MVP, extend the existing chat SSE stream in `packages/contracts/src/sse/chat.ts` rather than creating a second live-artifact SSE connection. `apps/web/src/providers/daemon.ts` should translate the SSE payload into a UI `AgentEvent` such as `kind: 'live_artifact_update'`, and `ProjectView.tsx` should use that event to refresh the live artifact list and auto-open the artifact using the same open-request flow used for produced files.

Suggested event:

```ts
type LiveArtifactEvent = {
  type: 'live_artifact_created' | 'live_artifact_updated' | 'live_artifact_refresh_completed';
  artifactId: string;
  title: string;
  previewUrl?: string;
  status: string;
};
```

## 11. Implementation plan

### Phase 0 — Contracts first

- Add this spec.
- Add shared TypeScript DTOs under `packages/contracts`, keeping the package pure TypeScript and free of Next.js, Express, filesystem/process APIs, browser APIs, SQLite, daemon internals, and sidecar control-plane dependencies.
- Add shared contract DTOs for `LiveArtifact`, `LiveArtifactSource`, and connector catalog entries. Runtime validation schemas belong under daemon source, especially `apps/daemon/src/live-artifacts/schema.ts`, and should consume or mirror the shared contract types without adding daemon internals to `packages/contracts`.
- Add or update contract files such as:
  - `packages/contracts/src/api/live-artifacts.ts`
  - `packages/contracts/src/api/connectors.ts`
  - `packages/contracts/src/sse/chat.ts`
  - `packages/contracts/src/examples.ts`
  - `packages/contracts/src/index.ts`
- Add fixture artifacts under `specs/2026-04-29-live-artifacts/examples/`.
- Extend `packages/contracts/src/errors.ts` with live artifact / connector error codes instead of defining a second error envelope.
- Define `html_template_v1` binding grammar and example `template.html + data.json`.

Exit criteria:

- Schemas reject raw provider response fields and credential-like values.
- Example artifact can be rendered from `template.html + data.json` through the preview contract.

### Phase 1A — Register static local live artifacts

- Implement daemon live artifact service.
- Implement project-scoped file storage under `<RUNTIME_DATA_DIR>/projects/<projectId>/.live-artifacts`.
- Add `/api/tools/live-artifacts/create` and `list`.
- Add `GET /api/live-artifacts?projectId=...` and `GET /api/live-artifacts/:artifactId`.
- Add run-scoped `OD_TOOL_TOKEN` for tool endpoints.

Exit criteria:

- A static `html_template_v1` artifact can be registered without connectors or refresh.
- The daemon rejects invalid paths, raw provider fields, and credential-like values.

### Phase 1B — UI preview integration

- Add `GET /api/live-artifacts/:artifactId/preview`.
- Render `template.html + data.json` into a sandboxed iframe response.
- Fetch live artifacts alongside project files and show them as virtual nodes in the artifact tree.
- Add a `LiveArtifactViewer` path in `FileViewer.tsx` that reuses the existing HTML viewer toolbar/iframe patterns.
- Add read-only `Preview`, `Source`, `Data`, `Provenance`, and `Refresh history` viewer tabs.

Exit criteria:

- UI can list and preview a registered live artifact.
- Preview does not require exposing `snapshots/` or implementation files as normal project files.

### Phase 1C — Built-in skill and wrapper command

- Add built-in `skills/live-artifact/SKILL.md`.
- Add `od tools live-artifacts ...` and connector command handlers from TypeScript source under `apps/daemon/src`.
- Inject daemon URL and short-lived tool token into skill preamble.

Exit criteria:

- Agent can create a live artifact using local data.
- UI can list and preview it.
- No MCP configuration is required.

### Phase 2 — Refresh runner

- Add `refreshes.jsonl` audit log.
- Implement manual refresh for local daemon tools.
- Implement per-artifact refresh lock, timeout, stale-write protection, and crash recovery.
- Preserve previous render on validation failure.
- Emit chat-stream UI refresh events and update viewer refresh loading/failure state.

Exit criteria:

- User can click Refresh and see updated data.
- Failed refresh leaves old preview intact and shows actionable error.

### Phase 3 — Connector catalog and read-only connector tools

- Port Monet connector catalog/service shape.
- Add connector endpoints.
- Add `/api/tools/connectors/list` and `/api/tools/connectors/execute`.
- Add read-only tool classification.
- Add first real read-only connector.
- Extend `live-artifact` skill references with connector usage instructions.

Exit criteria:

- Agent can query available connectors.
- Agent can create a refreshable artifact from a read-only connector.
- Refresh revalidates connector, account, tool, and approval policy.

### Phase 4 — Optional MCP wrapper

- Confirmation after the skill + wrapper path: MCP is not needed for MVP correctness because all supported agents can use `SKILL.md` plus `od tools ...` wrappers, and Phase 1C/Phase 3 command surfaces cover live artifact creation, listing, update, refresh, connector listing, and read-only connector execution. MCP is only worth adding as an additive compatibility layer for agents with mature MCP support and must not replace, weaken, or fork the daemon-owned service/policy path.
- Wrap the daemon's existing live artifact and connector services as an MCP server for agents that support MCP well.
- Do not make MCP required.
- Do not mutate global user MCP config automatically.

#### 11.5.1 Optional MCP server design

The MCP integration, if added, should be a **thin stdio adapter over the existing daemon tool endpoints**, not a second tool implementation. The MCP process should be launched only for an agent run that explicitly supports MCP and receives the same injected runtime environment as the wrapper CLI:

```text
MCP-capable agent
  ⇄ stdio MCP protocol
od mcp live-artifacts          # TypeScript source under apps/daemon/src, built into the od bin
  ⇄ local HTTP with Authorization: Bearer $OD_TOOL_TOKEN
/api/tools/live-artifacts/* and /api/tools/connectors/*
  ⇄ daemon live artifact, refresh, connector, auth, validation, and policy services
```

Design constraints:

- **Single policy path:** the MCP server must call the existing `/api/tools/*` endpoints using `OD_DAEMON_URL` and `OD_TOOL_TOKEN`. It must not import store/service modules to bypass token scoping, connector policy, output redaction, rate limits, or route validation.
- **Run scoped:** one MCP server instance is scoped to one agent run and one project through the bearer token. It exits when stdio closes; daemon token expiry/revocation remains authoritative.
- **Equivalent tools only:** expose MCP tools that mirror the CLI/API surface, with the same schemas and compact results:
  - `od_live_artifacts_create` → `POST /api/tools/live-artifacts/create`
  - `od_live_artifacts_list` → `GET /api/tools/live-artifacts/list`
  - `od_live_artifacts_update` → `POST /api/tools/live-artifacts/update`
  - `od_live_artifacts_refresh` → `POST /api/tools/live-artifacts/refresh`
  - `od_connectors_list` → `GET /api/tools/connectors/list`
  - `od_connectors_execute` → `POST /api/tools/connectors/execute`
- **No project overrides:** tool input schemas must not accept `projectId`; project/run scope is always derived from `OD_TOOL_TOKEN` by daemon routes.
- **No global config mutation:** OD may display or generate an ephemeral MCP launch descriptor for compatible agents, but must not edit user-level MCP config files automatically.
- **No primary-path dependency:** `SKILL.md`, `od tools ...`, and raw-token debugging remain unchanged and continue to work when MCP is disabled or unsupported.
- **Typed implementation:** project-owned MCP code should be TypeScript source under `apps/daemon/src` (for example `apps/daemon/src/mcp/live-artifacts-server.ts` plus small CLI dispatch in `apps/daemon/src/cli.ts`). Any JavaScript entrypoint must be generated build output or an explicitly documented compatibility artifact.

MCP tool errors should translate daemon `ApiErrorResponse` values into MCP tool errors without expanding secret-bearing details. Validation field details may be included only when they are already safe to return from the corresponding `/api/tools/*` route.

Exit criteria:

- Claude Code or another MCP-capable agent can discover equivalent tools through MCP.
- Skill + CLI path still works unchanged.

## 12. File-level landing plan

Likely new files:

```text
packages/contracts/src/api/live-artifacts.ts
packages/contracts/src/api/connectors.ts
packages/contracts/src/examples.ts
apps/daemon/src/live-artifacts/schema.ts
apps/daemon/src/live-artifacts/store.ts
apps/daemon/src/live-artifacts/render.ts
apps/daemon/src/live-artifacts/refresh.ts
apps/daemon/src/live-artifacts/routes.ts
apps/daemon/src/connectors/catalog.ts
apps/daemon/src/connectors/service.ts
apps/daemon/src/connectors/routes.ts
apps/daemon/src/tools/live-artifacts.ts
apps/daemon/src/tools/connectors.ts
skills/live-artifact/SKILL.md
skills/live-artifact/references/artifact-schema.md
skills/live-artifact/references/connector-policy.md
skills/live-artifact/references/refresh-contract.md
```

Likely touched files:

```text
packages/contracts/src/errors.ts
packages/contracts/src/index.ts
packages/contracts/src/sse/chat.ts
apps/daemon/src/server.ts
apps/daemon/src/skills.ts
apps/daemon/src/cli.ts
apps/web/src/providers/daemon.ts
apps/web/src/providers/registry.ts
apps/web/src/components/EntryView.tsx
apps/web/src/components/NewProjectPanel.tsx
apps/web/src/components/ProjectView.tsx
apps/web/src/components/DesignsTab.tsx
apps/web/src/components/FileWorkspace.tsx
apps/web/src/components/FileViewer.tsx
apps/web/src/components/PreviewModal.tsx
apps/web/src/types.ts
apps/web/src/prompts/system.ts
```

Keep the first implementation small: current daemon route handlers live in `apps/daemon/src/server.ts`, so either mount live artifact routes there first or add small TypeScript route modules that are imported by `server.ts`. Do not add project-owned `.js` source files; JavaScript should only be generated build output or an explicitly documented compatibility artifact.

## 13. Security and trust model

### 13.1 Daemon must enforce

- `/api/tools/*` requires a short-lived bearer `OD_TOOL_TOKEN`.
- Tool tokens are minted per agent run and bind `runId`, `projectId`, allowed endpoints, allowed operations, and expiry.
- `/api/tools/*` derives project/run scope from the token and rejects request-supplied project overrides.
- CORS for local daemon tool endpoints is closed by default; UI endpoints use the web app's normal origin/session checks.
- Defend against CSRF and DNS rebinding on localhost endpoints.
- Project ID exists and maps to the active workspace.
- All file paths stay inside the project workspace.
- Tool output size is bounded.
- Snapshot/history size and retention are bounded.
- Refresh execution has timeout and cancellation.
- Connector credentials never reach agent output or artifact files.
- Source input is minimized and redacted.
- Read-only refreshes cannot drift into write-capable tools.
- Preview responses use iframe sandboxing and restrictive CSP.

### 13.2 Skill must instruct

- Do not paste raw connector responses into `artifact.json`.
- Do not store tokens, headers, cookies, or credentials.
- Prefer summaries, normalized rows, and derived metrics.
- Keep `data.json` compact and preview-oriented.
- Use daemon tool endpoints for registration and refresh metadata.
- Use wrapper commands rather than constructing raw HTTP unless debugging.

### 13.3 UI must communicate

- Which connector/account backs the artifact.
- When it was last refreshed.
- Whether refresh is manual only.
- Why a refresh failed.

## 14. Non-goals

- No MCP-first implementation in MVP.
- No arbitrary write-capable connector refresh.
- No raw provider response storage.
- No multi-user auth model.
- No cloud-hosted connector broker in v1.
- No new canvas abstraction separate from OD's existing artifact/preview model.

## 15. Open questions

1. Should `od tools ...` be the only wrapper surface, or should generated per-project wrappers also be provided for easier agent access?
2. How should agent adapters advertise `shell` availability for skill gating?
3. How much refresh history should be retained before compaction?
4. Should failed refresh attempt payloads be retained in a hidden failed snapshot directory, or only summarized in `refreshes.jsonl`?

## 16. Acceptance criteria

- A built-in `live-artifact` skill can be discovered by the existing skill registry.
- An agent can create a live artifact without MCP.
- The daemon validates and persists live artifact metadata.
- The UI can list and preview the artifact.
- Manual refresh works for at least one local read-only source.
- Refresh failures are audited and do not destroy the last valid preview.
- Connector-backed refresh is read-only and revalidated before every run.
- `/api/tools/*` calls require run-scoped auth and cannot override project scope.
- No persisted artifact fixture contains raw credentials, headers, cookies, or full provider payloads.

## 17. Recommended first slice

Implement the smallest useful vertical slice:

1. `skills/live-artifact/SKILL.md`
2. `packages/contracts/src/api/live-artifacts.ts`
3. `apps/daemon/src/live-artifacts/schema.ts`
4. `apps/daemon/src/live-artifacts/store.ts`
5. `POST /api/tools/live-artifacts/create`
6. `GET /api/live-artifacts?projectId=...`
7. `GET /api/live-artifacts/:artifactId/preview`
8. UI list + virtual live-artifact node + sandboxed preview

This proves the skill-based interface and storage model before adding connectors, OAuth, refresh runner complexity, or MCP wrappers.
