Agent Preview

How the standalone brand-side agent demo combines persona, memory, files, tools, and a stateless chat

The Agent Preview is a standalone demonstration of one brand-facing persona agent. A brand can describe an offer, attach supporting material, and negotiate against a fixed snapshot of the talent's preferences. It is designed to make the agent's behaviour easy to test without creating accounts, sending email, or writing real offers.

The whole flow

flowchart TB Brand["Brand user"] subgraph Browser["Browser - standalone preview"] UI["Mike Tyson chat UI"] History["Current chat history"] Editor["Hidden prompt editor"] Local["Browser localStorage"] Prepare["Prepare attached files"] Editor <--> Local UI --> History UI --> Prepare end subgraph API["Stateless chat API"] Validate["Validate messages and prompt"] Convert["Convert UI messages to model messages"] System["Compose system prompt"] Tools["Expose scrapePublicUrl tool"] end Persona["Persona instructions"] Memory["Fixed talent memory snapshot"] Runtime["Protected runtime rules"] Model["Claude Fable 5"] Scraper["Safe public URL scraper"] Reply["Streamed agent reply"] Brand --> UI History --> Validate Prepare --> Validate Validate --> Convert Persona --> System Memory --> System Runtime --> System System --> Model Convert --> Model Tools --> Model Model -->|"URL tool call"| Scraper Scraper -->|"title and readable content"| Model Model --> Reply --> UI --> Brand

There is no server-side chat session. On each turn, useChat sends the current conversation, the selected prompt, and any attachments to the API. The API prepares model-compatible messages and streams the answer back. Reloading the page creates a new conversation.

What the agent is made of

Four inputs have different responsibilities. Keeping them separate is the central design rule.

InputWhat it controlsExample
PersonaHow the agent speaks and behavesShort, direct, no salesmanship; ask one thing at a time
MemoryWhat the agent knows about the talentHard-no categories, cash floors, required product trial, Vegas-only shoots
ConversationWhat the brand has said in this chatBrand, product, deliverables, budget, usage, proposed equity
Tools and filesWhat the agent can inspect during this turnA public product URL, PDF brief, image, or uploaded HTML file

The persona answers how to speak. The memory answers what is true. Tools answer what the agent can do. A tool never replaces the persona, and the persona never invents a capability.

How memory is read

The preview does not load memory from the Zooly database. Its initial memory is a JSON snapshot embedded below the --- MEMORY --- marker in the default system prompt.

flowchart LR Setup["Talent setup simulation"] --> Snapshot["JSON memory snapshot"] Snapshot --> Prompt["System prompt"] Prompt --> Agent["Brand-facing persona"] Snapshot --> Facts["Profile and communication style"] Snapshot --> Rules["Rates, hard nos, and must-haves"] Snapshot --> Goals["Talent setup met; brand offer open"] Snapshot --> Guidance["How to apply the rules"]

On every model call, the API combines:

  1. the persona instructions;
  2. the full memory snapshot;
  3. protected runtime instructions for tools and attachments; and
  4. the complete conversation so far.

The agent is instructed to treat memory facts as true, never quote or reveal the memory, and never invent a preference that is absent. The brand should hear the resulting commercial position, not the internal JSON or reasoning behind it.

This snapshot represents the output that a production talent-setup flow would eventually provide. In the complete architecture, durable memory seeds a room, the Planner maintains room memory, and Recall selects what a persona sees. The preview deliberately replaces that pipeline with one fixed document.

The decision logic

The system prompt gives the persona three jobs: agree, push back, or ask.

flowchart TD Message["Brand sends an offer or answer"] --> Supplied{"Enough information supplied?"} Supplied -->|"No"| Ask["Ask for one missing required item"] Supplied -->|"Yes"| HardNo{"Hard-no category or rule violated?"} HardNo -->|"Yes"| Reject["Push back and name the blocking rule"] HardNo -->|"No"| Terms{"Budget and deal terms meet memory?"} Terms -->|"No"| Counter["Push back with the required boundary"] Terms -->|"Yes"| Trial{"A product trial is required?"} Trial -->|"Yes"| Conditional["Settle terms with trial as a condition"] Trial -->|"No"| Settle["Settle terms"] Conditional --> Approval["Take agreed terms to talent for final approval"] Settle --> Approval

Agree means the terms are settled for review; it does not execute or sign a deal. The persona may negotiate within the talent's mandate, but every settled outcome remains pending final talent approval outside this standalone chat.

The order of checks matters:

  • Missing information produces a focused question, not an assumption.
  • A complete but prohibited offer is rejected even when the budget is high.
  • A complete qualifying offer can be settled without pretending it is signed.
  • The agent should ask one question at a time and avoid repeating the brand's last message.

What happens during a turn

sequenceDiagram autonumber participant Brand participant UI as useChat UI participant API as Preview API participant Model as Claude Fable 5 participant Tool as scrapePublicUrl Brand->>UI: message, files, or URL UI->>API: full history + prompt + attachments API->>API: validate and convert inputs API->>Model: system prompt + model messages + tools alt A public URL needs inspection Model->>Tool: scrapePublicUrl(url) Tool->>Tool: validate DNS, redirects, type, size, timeout Tool-->>Model: title + readable page content Model-->>API: final answer using the tool result else No URL tool is needed Model-->>API: answer from memory, chat, and files end API-->>UI: streamed response UI-->>Brand: agent reply

The API uses a bounded multi-step run. A URL tool call completes one generation step, its result is added to the model context, and the model gets another step to produce the human-facing answer.

URLs are real tool calls

A URL is not silently fetched before generation. The API exposes a typed AI SDK tool named scrapePublicUrl. Protected runtime instructions require the model to call it before discussing a public URL.

The tool:

  • accepts only public HTTP or HTTPS URLs;
  • rejects localhost, private networks, embedded credentials, and unsafe DNS results;
  • follows at most three validated redirects;
  • times out after eight seconds;
  • reads at most 250 KB from HTML, plain text, or JSON responses; and
  • returns the page title and up to 8,000 characters of readable content.

The model sees the tool result, not a claim that a page was opened. If the tool cannot access a page, the agent must say so and ask the brand to paste or attach the relevant content.

How files are read

Files are not uploaded to Zooly storage. They are prepared in the browser and sent as AI SDK message parts for the current request.

File typeProcessing
Text, Markdown, CSV, JSONRead in the browser, compacted, and supplied as text context
HTMLParsed without executing scripts; visible and embedded template content is extracted
PDFSent as a native model file input
ImageConverted from an AI SDK file part to a native multimodal image input

The composer accepts up to four attachments. Text and HTML inputs may be up to 25 MB and are reduced to at most 60,000 characters of extracted context. Binary files may be up to 10 MB. The default model supports native file input and image vision, so an image does not need a separate OCR tool.

Hidden prompt editor

Appending ?prompt-editor=1 enables an internal editing mode. It is hidden from ordinary preview visitors.

The editor supports two paths:

  • Advanced edit changes the complete prompt directly.
  • AI Assist accepts a plain-language request and returns a structured proposal with a compact change summary, warnings, and suggested tests.

AI Assist keeps instructions and memory separate. Behavioural requests update the persona instructions. Explicit talent-fact requests, such as changing a minimum rate, can update the memory snapshot and show before-and-after values for review. Nothing is applied until the operator accepts the proposal.

The edited prompt is stored only in that browser's localStorage. Saving it starts a new chat so old responses are never mixed with new instructions. The prompt is not written to a Zooly account or database.

Stateless boundaries

The preview intentionally does not:

  • require login or create an account;
  • read or write production talent memory;
  • persist chat messages on the server;
  • create or update a real offer;
  • send email; or
  • bind the talent to an agreement.

Its purpose is behavioural validation: change the prompt or memory, start a new chat, replay the same offer, and compare the result.

Code map

ResponsibilityLocation
Preview page and useChat UIapps/zooly-app/app/(pages)/agent-preview/mike-tyson/MikeTysonAgentPreview.tsx
Stateless chat endpoint and tool loopapps/zooly-app/app/api/agent-preview/chat/route.ts
URL validation and readable-content extractionpackages/z-agent/srv/src/link-scrape.ts (shared with the real agents)
Persona and fixed memory snapshotapps/zooly-app/lib/agent-preview-prompt.ts
AI-assisted prompt proposalsapps/zooly-app/app/api/agent-preview/prompt-generator/route.ts

The public demo route is /agent-preview/mike-tyson. The hidden editor route is /agent-preview/mike-tyson?prompt-editor=1.