Skip to content

apankov1/whitney-museum-opal-agent

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Whitney Museum of American Art — Opal Agent Examples

A complete Opal agent implementation for a cultural institution client, demonstrating three levels of platform capability: specialized agents, external tool integrations with a live public API, and multi-step workflow orchestration.

See it in action: output/demo-run.md — full E2E output from all three agents.

Quick Start

cd tools
npm install
cp .env.example .env           # add your ANTHROPIC_API_KEY

# Start the tool service
npm run dev &

# Run the full content pipeline (writer → reviewer)
npm run harness -- run ../agents/whitney-exhibition-content-workflow.json \
  --agents-dir ../agents \
  -p exhibition_title="Biennial 2025" \
  -p artist_names="Various" \
  -p exhibition_description="The Whitney Biennial returns" \
  -p opening_date="March 2025" -v

Client Context

The Whitney Museum of American Art is a major NYC institution focused on contemporary American art. Their digital team uses Optimizely for:

  • Exhibition landing pages — high-traffic pages that spike at exhibition launches and closings
  • Membership & ticketing funnels — conversion-optimized flows with A/B testing
  • Multi-channel content — coordinated messaging across web, email, social, and in-gallery signage

These agents address real operational needs: analyzing experiment results, generating on-brand content at scale, and enforcing quality standards before publication.

Data Sources

A key differentiator of this implementation: it connects to real data, not mocked inputs.

Whitney Public API (whitney.org/api)

The Whitney exposes a fully public, unauthenticated REST API returning JSON. No API key required.

Endpoint Returns Used By
/api/exhibitions Exhibition titles, dates, curatorial descriptions, URLs get_exhibitions tool
/api/artworks 26,995 works — metadata, images, alt text, visual descriptions search_collection tool
/api/artists 4,000+ artists with biographical data search_collection tool
/api/events Events from 2008 onward Available for future agents
/api/guides Audio guides with stops Available for future agents

Query syntax: Ransack-style — q[field_matcher]=value. Supports cont (contains), eq (equals), gt/lt (greater/less than), true/false booleans. Sorting via q[s]=field+asc|desc.

API docs: https://whitney.org/about/website/api

Whitney Open Access (GitHub)

Nightly-updated CSV datasets under CC0 public domain license:

Accessibility Data (Built Into the API)

The API includes structured accessibility fields per artwork:

  • alt_text — human-written alternative text
  • ai_alt_text — AI-generated alternative text
  • visual_description — detailed verbal description for blind/low-vision visitors
  • audio_description — exhibition-level audio descriptions

This is not common — Whitney has invested in accessibility at the data model level, which makes it possible for our tools to surface and validate accessibility content programmatically.

Brand Voice (Derived from Public Content)

No published style guide exists. Voice rules in our check_brand_voice tool are derived from:

  • Actual exhibition wall text patterns (see Whitney's "Words on Walls" article)
  • Website copy tone: inviting, action-oriented ("Explore", "Dive Into"), avoids academic jargon
  • Social media tone: conversational on Threads/Instagram, more formal on web
  • DEI language practices observed in collection descriptions and accessibility pages
  • Visual identity by Experimental Jetset — Neue Haas Grotesk typeface, "responsive W" mark

What's Included

1. Exhibition A/B Test Analyzer (whitney-exhibition-ab-test-analyzer.json)

Type: Specialized Agent | Creativity: 0.2 | Inference: complex | Tools: none

A standalone agent that analyzes A/B test results from Optimizely experiments. Paste in test data, and it returns a structured report with statistical assessment, experimentation pitfall checks (peeking, novelty effects, Simpson's paradox), museum-specific context (traffic spikes, seasonal patterns), and a clear verdict: Ship, Extend, Kill, or Investigate.

Key design decisions:

  • creativity: 0.2 — statistical analysis needs precision, not creativity
  • enabled_tools: [] — pure analysis on provided data, no external calls needed
  • Museum-specific pitfall checks (exhibition launch spikes, member preview events, seasonal tourism) that a generic analytics agent would miss

Matching instruction template: instructions/CLEAR - Exhibition AB Test Analysis.txt

2. Exhibition Content Pipeline (whitney-exhibition-content-workflow.json)

Type: Workflow Agent (2 steps)

An end-to-end content workflow that takes a curatorial brief and produces publication-ready exhibition content:

[Curatorial Brief] → Content Writer → Content Reviewer → [Approved Package or Revision List]

Step 1 — Content Writer (whitney-content-writer.json)

  • Creativity: 0.7 — content generation benefits from higher temperature
  • Takes exhibition details (title, artist, description, dates, location)
  • Generates 4 content types: gallery wall text, website landing page, email announcement, 3 social media variants
  • Enforces Whitney voice guidelines directly in the prompt

Step 2 — Content Reviewer (whitney-content-reviewer.json)

  • Creativity: 0.2 — review is analytical, not generative
  • Calls external tools (check_brand_voice, check_accessibility) on each content section
  • Produces a consolidated review: pass/fail per section with specific revision list
  • Content is APPROVED only if ALL sections pass both checks

3. Whitney Content Tools (tools/)

Type: External Tool Service (Hono + Cloudflare Workers)

A TypeScript service deployed as a Cloudflare Worker, exposing four tools via /discovery and /registry endpoints:

get_exhibitions — Live API

Fetches real exhibition data from whitney.org/api/exhibitions. Filters by status (current, upcoming, past) and keyword search. Returns titles, dates, URLs, and curatorial descriptions. This enables agents to pull real data instead of requiring manual input.

search_collection — Live API

Searches the Whitney's 26,995-work collection. Filters by artist name, classification (Paintings, Sculpture, Video, etc.), keyword, and on-view status. Returns artwork metadata with accessibility fields (alt text, visual descriptions). Enables content agents to reference real collection data.

check_brand_voice — Content Analysis

Evaluates text against 5 Whitney-derived voice rules:

Rule What it checks
WBV-001 Institutional tone (no casual slang or superlatives)
WBV-002 Accessible art language (flags academic jargon)
WBV-003 Inclusive language (DEI-aligned terminology)
WBV-004 Active voice preference
WBV-005 Consistent Whitney terminology ("exhibition" not "exhibit")

Sensitivity adjusts by content type — social posts allow slightly more casual tone than wall text.

check_accessibility — Readability Analysis

Evaluates content readability and accessibility:

Check What it flags
ACC-001 Flesch-Kincaid reading level vs target (default: grade 8)
ACC-002 Sentences exceeding 30 words
ACC-003 Paragraphs exceeding 150 words without breaks
ACC-004 Abbreviations used without definition
ACC-005 Image references without descriptive language

Setup:

cd tools
npm install
cp .env.example .env           # fill in ANTHROPIC_API_KEY (+ optionally Cloudflare creds)

# Start tool service locally
npm run dev
# http://localhost:8787/discovery

# Run an agent via the local test harness (requires ANTHROPIC_API_KEY in .env)
npm run harness -- run ../agents/whitney-content-writer.json \
  -p exhibition_title="Biennial 2025" \
  -p artist_names="Various" \
  -p exhibition_description="The Whitney Biennial returns" \
  -p opening_date="March 2025" -v

# Deploy to Cloudflare (after `npx wrangler login`)
npm run deploy

Architecture

 ┌──────────────────────────────────────────────────────────────────────┐
 │                        EXTERNAL DATA SOURCES                        │
 │                                                                     │
 │  whitney.org/api/exhibitions ──┐                                    │
 │  whitney.org/api/artworks ─────┤── Public REST API (no auth)        │
 │  whitney.org/api/artists ──────┘                                    │
 │                                                                     │
 │  github.com/whitneymuseum/open-access ── CC0 CSV (nightly update)   │
 └──────────────────┬───────────────────────────────────────────────────┘
                    │
                    ▼
 ┌──────────────────────────────────────────────────────────────────────┐
 │               TOOL SERVICE (Hono + Cloudflare Workers)              │
 │               tools/src/index.ts                                    │
 │               Deployed to: Cloudflare Workers                       │
 │               Discovery: /discovery | /registry                     │
 │                                                                     │
 │  ┌──────────────────┐  ┌────────────────────┐                       │
 │  │ get_exhibitions   │  │ search_collection  │  ← Live API wrappers │
 │  │ (current/upcoming │  │ (artist, type,     │                       │
 │  │  past, search)    │  │  keyword, on-view) │                       │
 │  └──────────────────┘  └────────────────────┘                       │
 │                                                                     │
 │  ┌──────────────────┐  ┌────────────────────┐                       │
 │  │ check_brand_voice│  │ check_accessibility│  ← Content analysis   │
 │  │ (5 voice rules,  │  │ (Flesch-Kincaid,   │                       │
 │  │  scored output)  │  │  5 checks)         │                       │
 │  └──────────────────┘  └────────────────────┘                       │
 └──────────────────┬───────────────────────────────────────────────────┘
                    │
                    ▼
 ┌──────────────────────────────────────────────────────────────────────┐
 │                         OPAL PLATFORM                               │
 │                                                                     │
 │  ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐  │
 │    WORKFLOW: Exhibition Content Pipeline                            │
 │  │ (whitney-exhibition-content-workflow.json)                    │  │
 │                                                                     │
 │  │  Trigger ──→ ┌─────────────────────┐                         │  │
 │     (message)   │ Content Writer      │  creativity: 0.7           │
 │  │              │ Wall text, web copy,│  inference: complex      │  │
 │                 │ email, social x3    │                             │
 │  │              └─────────┬───────────┘                         │  │
 │                           │ content package (shared memory)         │
 │  │              ┌─────────▼───────────┐                         │  │
 │                 │ Content Reviewer    │  creativity: 0.2           │
 │  │              │ Calls tools:       │──→ check_brand_voice     │  │
 │                 │                    │──→ check_accessibility       │
 │  │              └─────────┬───────────┘                         │  │
 │                           │                                         │
 │  │              APPROVED or REVISION LIST                       │  │
 │  └ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘  │
 │                                                                     │
 │  ┌────────────────────────────────────────┐                         │
 │  │ A/B Test Analyzer (standalone)         │  creativity: 0.2       │
 │  │ No tools — pure analysis on user input │  inference: complex    │
 │  │ Verdict: Ship / Extend / Kill / Invest │                         │
 │  └────────────────────────────────────────┘                         │
 │                                                                     │
 │  Built-in tools also available:                                     │
 │  browse_web · search_web · get_today                                │
 └──────────────────────────────────────────────────────────────────────┘

 ┌──────────────────────────────────────────────────────────────────────┐
 │                      INSTRUCTION TEMPLATES                          │
 │  instructions/                                                      │
 │  └── CLEAR - Exhibition AB Test Analysis.txt                        │
 │      (C) Context → (L) Layout → (E) Example → (A) Action → (R)     │
 └──────────────────────────────────────────────────────────────────────┘

How Opal Connects to External Tools

Opal discovers tools at runtime via HTTP endpoints. Two patterns exist in the ecosystem:

1. Opal Tools SDK (Express + decorators)

The official @optimizely-opal/opal-tools-sdk package provides @tool decorators on Express. Auto-generates /discovery. Best for teams already on Express who want the least boilerplate.

2. Custom HTTP service (what we use)

Any HTTP service that exposes /discovery or /registry returning tool schemas. We use Hono on Cloudflare Workers — zero cold start, edge-deployed, no Express dependency. The tool metadata is defined as a plain array and the routes are registered in a loop:

const app = new Hono();
app.get('/discovery', (c) => c.json({ tools: TOOLS.map(t => t.meta) }));
for (const tool of TOOLS) {
  app.post(`/tools/${tool.name}`, async (c) => { /* ... */ });
}
export default app;

3. Built-in Platform Tools

browse_web, search_web, get_today — no deployment needed, enabled per agent via enabled_tools.

How agents discover tools

  1. Tool service exposes /discovery or /registry returning JSON
  2. Endpoint returns function names, descriptions, parameter schemas
  3. Opal reads this at runtime to know what tools the agent can call
  4. Agent's enabled_tools array controls which tools are available to it
  5. The LLM decides when to call a tool based on the description

Why This Matters for a Cultural Institution

  1. Real data, not demos — The tool service calls Whitney's actual API. An agent can pull today's current exhibitions, search the real collection, and generate content grounded in facts — not hallucinated metadata.

  2. Content at exhibition scale — Major museums launch 10-20 exhibitions per year. Each needs wall text, web copy, emails, and social posts in a consistent voice. This workflow turns a 2-week content cycle into hours.

  3. Brand consistency without bottlenecks — Brand voice rules are codified in the tool, not trapped in one editor's head. New team members and freelancers produce on-brand content from day one.

  4. Accessibility as a first-class concern — Museums serve diverse audiences including ESL visitors, children, and visitors with disabilities. Baking readability checks into the pipeline ensures every piece of content is evaluated. The Whitney's own API provides alt_text, ai_alt_text, and visual_description fields — our tools surface and validate these.

  5. Experimentation culture — The A/B test analyzer makes experiment results accessible to non-technical stakeholders (curators, directors), encouraging data-informed digital experience decisions.

File Map

whitney-museum-opal-agent/
├── README.md                                    ← you are here
├── .github/workflows/
│   └── deploy.yml                               ← CI/CD: deploys tools/ to Cloudflare Workers
├── output/
│   └── demo-run.md                              ← full E2E output from all three agents
├── agents/
│   ├── whitney-exhibition-ab-test-analyzer.json ← specialized agent (standalone)
│   ├── whitney-content-writer.json              ← specialized agent (workflow step 1)
│   ├── whitney-content-reviewer.json            ← specialized agent (workflow step 2)
│   └── whitney-exhibition-content-workflow.json  ← workflow orchestration
├── tools/
│   ├── package.json
│   ├── tsconfig.json
│   ├── wrangler.toml                            ← Cloudflare Workers config
│   ├── .env.example                             ← API keys + deployment credentials
│   └── src/
│       ├── index.ts                             ← tool service (Hono, 4 tools)
│       ├── cli.ts                               ← test harness CLI entry point
│       ├── runner.ts                            ← single-agent agentic loop
│       ├── workflow.ts                          ← multi-step workflow orchestration
│       ├── tool-client.ts                       ← discovery fetch + tool call routing
│       ├── config.ts                            ← agent JSON loading + validation
│       ├── prompt.ts                            ← [[param]] template rendering
│       └── types.ts                             ← shared TypeScript interfaces
└── instructions/
    └── CLEAR - Exhibition AB Test Analysis.txt  ← CLEAR instruction template

About

Opal agent implementation for the Whitney Museum of American Art — specialized agents, external tool integrations with live API, and multi-step workflow orchestration

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors