Contents

Documentation · OF1 Product

The page your visitor sees isn't the page you published. OF1 Product turns a static site into a surface that adapts per visitor. This page explains how a page is generated, what signals drive it, what data is (and isn't) stored, and how to integrate the service into your own site. It describes the of1-gen-web-service — the runtime that produces personalized pages.

What of1 is

of1 is an AI service that assembles a web page in real time for the individual visitor in front of it. Instead of serving one page authored once by hand, it reads the visitor's context, works out what they're trying to do, retrieves the most relevant content from your own corpus, and composes a page that fits that intent — every time, with no rules to write and no segments to maintain.

The critical design decision is that the LLM never writes HTML. It decides what to show and in what order; your design system decides how it renders. That keeps output on-brand, accessible, and impossible to break — while still being fully personalized.

of1 runs on Adobe Edge Delivery Services — Adobe's edge-native web platform. Edge Delivery Services is a hard requirement: of1 assembles pages from the block-based content model that EDS provides and delivers them at the edge.

Adapts

Per visitor, per intent

Every element is reconsidered for the person viewing it — not a segment they were bucketed into.

On-brand

Blocks, not markup

The model selects from 30+ pre-built, CSS-styled blocks. It never invents new markup.

Grounded

Your content, via RAG

Copy is filled from your products, features and FAQs — retrieved by vector search, not hallucinated.

Private

Signals stay client-side

Behavioural context is gathered in the browser and sent per request. of1 stores no visitor profile.

The core idea

For decades, marketing meant shouting at categories and cohorts. Your visitors now spend all day with tools that respond to their intent — then land on your one static page.

Segments are a confession that you don't know your customer well enough.

of1 removes the segment. There are no rules to write, no content matrix to maintain, no A/B variants to fork. The page is generated for the one person viewing it — and it should feel authored, not targeted. As the deck puts it: most AI wants you to know it's there; ours wants you to forget.

Request lifecycle

A single call to POST /api/generate runs an ordered pipeline at the edge. Results stream back as newline-delimited JSON (NDJSON) so the page renders block-by-block as it's produced.

  1. Capture context — at the edge, from the browser. The request carries the visitor's query, plus optional signals: browsing[] (URL, dwell time, scroll depth), interests[], a pre-classified intent, segment (device, region) and acquisition (UTM, referrer). A tenant id identifies the brand.
  2. Analyze behaviour — classifies the traffic source (shopping / social / search / email from the referrer) and derives a journey stage — awareness → consideration → decision — from session depth and dwell time. This frames the tone and depth of the page.
  3. Classify intent — the query is sorted into one of comparison, recommendation, deep-dive, budget or discovery, by LLM (default) or keyword fallback. Personas and use-cases configured for the tenant are matched in parallel.
  4. Retrieve content (RAG) — the query is embedded and used to search a per-tenant vector index over your products, features and FAQs. Up to ten ranked, relevance-scored results are returned — this is the grounded content the page is built from.
  5. Build the prompt — brand voice, optional brand-governance rules, the block/template guide, the retrieved content, and the analyzed intent are compiled into a system + user prompt for the model.
  6. Generate & stream — the model emits structured JSON describing the page. The worker streams it as NDJSON events — section (one per block), suggestions (follow-up prompts), debug (timings, model, token usage) and done — so the client renders progressively.

The two-phase LLM

When template routing is enabled for a tenant, the model runs twice: first it picks a layout that fits the intent, then it fills that layout with retrieved, on-brand content.

[diagram placeholder — recreate as a visual asset: "From visitor to page · one request, two phases" — visitor context → LLM reasoning engine (two phases) → personalized page. No HTML invented, no CSS touched — the LLM only decides which blocks, in what order, and what fills them.]

In Phase 1 the model classifies intent and picks one template from the tenant library (e.g. first-full-recommendation) — a template is just a fixed order of blocks, and the model never reorders it. In Phase 2 it fills each block's slots from RAG-retrieved content, shaped by the brand layer (voice, compliance, design tokens); image URLs are validated against an allow-list before render.

The output is a complete page — assembled from pre-styled blocks — that feels authored rather than generated. The default flow works the same way without a template step: the model composes hero / columns / cards sections directly.

Why two phases. Separating structure (which blocks, in which order) from content (what goes in them) keeps each model call small and verifiable, and means the layout is always one of a known, tested set.

Blocks & templates

Why the LLM picks blocks instead of writing HTML — and how templates map intent to a layout.

A typical web page is 10,000–50,000 tokens of HTML. Asking an LLM to emit that raw is slow, brittle, and unsafe: it hallucinates class names, breaks CSS and drops accessibility attributes, and every page would need validating. of1 sidesteps this by reusing the separation of concerns every CMS already has:

[diagram placeholder — recreate as a visual asset: "Separation of concerns · the LLM decides what, not how" — a 3-layer grid: styling / structure / content]

In AEM Edge Delivery Services a block is simply a named table in a document — authors write them in Google Docs or Word, developers style them with CSS. of1 selects from 30+ of these pre-built blocks; it never invents markup. A template is a block order that fits a visitor's intent, grouped into families:

Inside one template — first-full-recommendation — is a fixed block order:

  1. hero
  2. product-recommendation
  3. comparison-table
  4. verdict-card
  5. recipe-cards

[diagram placeholder — recreate as a visual asset: "Inside one template · a fixed block order" — the LLM fills each block, it never reorders them.]

Levels of generation

of1 generates at five levels, from picking one approved image to writing a whole page. The levels are independent: a page can use one, several or none, and every level falls back to the page as published. A team that isn't ready to let a model write copy can start at levels 1 and 2, where nothing is written at all.

  1. Level 1 · Image selection. {{generative-image: collection}} in an image's alt text. A cosine match against images you've approved; no LLM call. See Generative images and fragments.
  2. Level 2 · Fragment selection. {{generative-fragment: type}} as a Fragment block's link text. The same match, over your fragments; no LLM call.
  3. Level 3 · Generative components. A block variant, an inline marker or a section scope. The model rewrites only the text you mark. See Generative components.
  4. Level 4 · Automatic personalization. No markup. of1-edge-proxy reads the visitor and rewrites a few key elements, such as the headline, subhead and call to action, through POST /api/personalize.
  5. Level 5 · Full-page generation. POST /api/generate assembles a page from your block library and templates, filled from your tenant content. See Request lifecycle.

Levels 1 to 3 share one path through the proxy, and a page that carries any of their markup skips level 4: an author's explicit intent outranks the proxy's guess at which lines matter. Up to 20 marked slots are filled per page, in DOM order.

Generative components

Everything above describes of1-gen-web-service assembling a page from nothing. of1-edge-proxy does the opposite: it fronts a page you've already published and rewrites only the pieces an author marked, leaving structure and CSS exactly as authored.

There's no code to write. An author marks intent using ordinary DA conventions — a block variant, a Section Metadata key, or a directive typed straight into a sentence — and the proxy resolves it into an LLM instruction at request time, per visitor. Three mechanisms cover every shape of authored content: a whole block, one sentence, or plain body text with no block at all.

A · Block variant — whole block, every text cell

Append generative to any block's name and every text cell inside it becomes a slot. Cells that carry only an image are left alone. A Section Metadata generative-instruction key next to it sets one instruction for every cell in the block — otherwise the proxy defaults to "adapt this to the visitor."

B · Inline marker — one sentence, anywhere

{{generate: instruction}} typed directly into a paragraph. Pair it with {{default: fallback}} to control exactly what a reader sees when generation is skipped or fails — a reader must never see raw markup.

S · Section scope — plain body text, no block

For ordinary headings and paragraphs with no block at all, a Section Metadata generative-scope key lists which tags are eligible (h1–h6, p). Fail-closed by design: a section with no scope key generates nothing, however it's styled.

What each mechanism looks like authored in DA:

Mechanism A — a section authored as a Cards (generative) block, with a Section Metadata generative-instruction set alongside it. Every text cell becomes a slot; image cells carry no text and must survive untouched.

Demo A — generative block variant (cards, Spanish)

Authored as Cards (generative), with a section-wide instruction in this section's metadata. Every text cell becomes a slot; the image cells carry no text and must survive untouched.

cards (generative)

Precision Brewing

Every shot is measured to the gram, so the result never depends on guesswork.

Built to Last

Stainless steel and brass age with grace, not with a replacement cycle.

section-metadata
generative-instruction Translate the text to Spanish.

Mechanism B — an inline marker typed straight into a paragraph: {{generate: Reframe this for a home barista, two sentences}}{{default: Consistent tamp pressure is the difference between a good shot and a great one. Ten thousand reps later, your wrist remembers what your eyes can't measure.}}

Demo B — inline marker (one sentence, anywhere)

No block, no Section Metadata — authored as an ordinary sentence, with the instruction and its fallback typed straight into the paragraph:

{{generate: Reframe this for a home barista, two sentences}}{{default: Consistent tamp pressure is the difference between a good shot and a great one. Ten thousand reps later, your wrist remembers what your eyes can't measure.}}

Renders as — the fallback shown to this reader: "Consistent tamp pressure is the difference between a good shot and a great one. Ten thousand reps later, your wrist remembers what your eyes can't measure."

Mechanism S — plain body text, opted in by Section Metadata alone: a heading "Water quality" and a paragraph on filtering, with Section Metadata generative-scope set to h3, p and generative-instruction set to "Translate the text to Japanese."

Demo S — section scope, no block (heading + paragraph, Japanese)

Plain body text, opted in by Section Metadata alone — generative-scope lists which tags are eligible. No block, no Section Metadata key, no generation.

Water quality

Filtered to under 50ppm hardness, so the coffee's own flavor leads — not the minerals in your tap.

section-metadata
generative-scope h3, p
generative-instruction Translate the text to Japanese.

Fails open, always. Every mechanism serves the authored fallback — the visible text, or an explicit {{default:}} — on a slow endpoint, a rejected slot id, or missing visitor consent. A reader can never end up with broken markup or a blank block; the worst case is simply the page as published.

Generative images and fragments

Levels 1 and 2 run through the same proxy path as generative components, but they make no LLM call. The proxy embeds the visitor's context once, then picks the closest item by cosine similarity from a set you approved ahead of time. Nothing is written. The only thing that changes is which image or fragment the visitor gets.

Generative images

Type {{generative-image: collection}} into an image's alt text. The collection names a set in your generative-images sheet. The authored image stays in place as the fallback, and the marker is stripped before the page is served, so a reader never sees it.

hero

Coffee, chosen for you

Image alt text: {{generative-image: hero}}

A visitor arriving from a Christmas campaign gets the gift-guide photo; one who has been reading about cycling gets the cycling photo. With no match, the authored photo stays.

The sheet is a multi-sheet DA document with two tabs: collections (id, ratio, description) and images (id, collection, name, description, tags, alt, url). Each image's name, description and tags are embedded at sync time. A row with no url is left out until the image exists.

Generative fragments

Use {{generative-fragment: type}} as the link text of a Fragment block, and point the link at a real fragment. That link is the fallback: of1 only swaps the href when it finds a better match, and it strips the marker either way.

fragment

{{generative-fragment: promo-banner}}

links to /fragments/promos/spring-sale

A winter-campaign visitor gets the autumn-ritual promo, a Christmas visitor gets the gift promo, and everyone else keeps the spring sale.

Candidates live in a generative-fragments sheet with a types tab (id, description) and a fragments tab (id, type, url, name, description, blocks, tags). The block list is embedded along with the prose, so a fragment built around a testimonial and one built around a pricing table stay apart even when their descriptions sound alike.

Publish both sheets with the rest of your tenant files (see Tenant configuration) and run a tenant sync.

Picked per visitor, not per segment. Text slots are cached per audience segment for five minutes. Image and fragment picks skip that cache, because they depend on the visitor's own signals, which the segment key leaves out on purpose.

Generative templates (DA blocks)

A generative component rewrites one element on a page that already exists. A template is the other end of the spectrum: the full block order of1-gen-web-service assembles from nothing (see Blocks & templates above). Authoring a template means building the block library it draws from — not the template itself.

Every block name a template references — hero, product-recommendation, comparison-table, verdict-card — is a real DA block: built and CSS-styled once by your developers, exactly like any other EDS block. The LLM only ever picks names out of that library and fills their slots with RAG-retrieved content; it never invents a block and never touches the CSS. Once a block exists in the library, it's reusable across every template that names it.

One block from the library, authored exactly like any other DA block — no generative markup needed here: a Verdict Card block with an image cell and a text cell ("Verdict" / "Our pick for you, in one line — filled at generation time, from your own product data"). Authored once, styled once, then reused as the target shape for every generated page that includes a verdict-card.

Template pages are authored, previewed, and published exactly the same way as any other DA page — hero, comparison-table, spec-badges and product-card below are just page content, nothing generative-specific about the mechanics. Here's a full template page as authored in DA, four sections in one document: Compare Arco Machines.

Compare Arco Machines

Side-by-side comparison to help you choose between two or more Arco espresso machines based on price, boiler type, and use case.

hero

Find the Right Arco for You

Compare boiler type, heat-up time, and pressure control to see which machine fits your routine.

section-metadata
Template Intent comparison
Template Description Introduces a head-to-head comparison between two to four Arco machines or grinders

Feature Comparison

comparison-table
Feature Arco Primo Arco Doppio
Price €299 €549
Boiler Type Thermoblock Dual boiler (brass brew, stainless steam)
Heat-Up Time 90 seconds 3 minutes
Best For Quick, reliable morning espresso Milk drinks and precision extraction
section-metadata
Template Min Items 2
Template Max Items 4

Quick Facts

spec-badges

Quick signals before the full comparison

Use these to narrow down before reading the detailed breakdown above.

Boiler Dual boiler Best when milk drinks are frequent
Warm-up 3 minutes Fast enough without the compromises of a thermoblock
section-metadata
Template Min Items 3
Template Max Items 4

Shop the Machines

product-card

Primo

€299

Our signature single-boiler machine for the home barista.

morning-minimalist, upgrader, non-barista

View Details

Doppio

€549

Dual-boiler performance. Steam and brew simultaneously without compromise.

upgrader, morning-minimalist, craft-barista

View Details

section-metadata
Template Min Items 2
Template Max Items 4

The template itself isn't a DA page — it's a name and an ordered list of block names, declared in templates.json:

templates.json

{

··"first-full-recommendation": {

····"blocks": ["hero", "product-recommendation", "comparison-table", "verdict-card", "recipe-cards"]

··}

}

Phase 1 of generation (see The two-phase LLM) picks one template name that fits the visitor's intent; Phase 2 fills each named block in that fixed order. A tenant that skips template routing can instead ship a block-guide.json — a looser set of rules the model composes from directly, without naming a fixed sequence up front.

Two authoring surfaces, one library. Generative components and generative templates draw from the same idea — the LLM chooses what and in what order, never how — but they're separate authoring surfaces. Components mark up a page that already exists, one element at a time. Templates and their blocks are built once, ahead of time, as a reusable library of1-gen-web-service assembles from on every request.

Try it on a page you've already published. Nothing above needs a code change or an SDK embed on an existing EDS site. of1-edge-proxy fronts your site the same way aem.page / aem.live already does — swap the host and both generative components and the /of1 template route work immediately: main--<repo>--<org>.of1.live instead of main--<repo>--<org>.aem.live (or the prev-- prefix for a preview host). For example, https://main--arco2--froesef--dev.of1.live/ serves the same content as the site's aem.live host, but already generative-component- and template-aware.

Signals captured

Context is gathered client-side and passed on the request. It describes the current session — not a stored identity.

Privacy — never leaves the browser. These signals are assembled client-side and sent only as part of the generation request. of1 does not set tracking cookies or build a cross-session visitor profile. The visitor's IP (from cf-connecting-ip) is used for rate-limiting only and is not written to storage.

Beyond the browser. The context model is open-ended — anything that helps describe who the visitor is and what they need can be passed on the request. The client-side signals above are the zero-setup default, but an integrator can enrich them with consented, first-party data:

Adobe profile

AEP & Real-Time CDP

The Real-Time Customer Profile from Adobe Experience Platform — audiences, computed attributes and edge-resolved identity.

Known customer

CRM & account

Lifecycle stage, tier, entitlements and purchase history for a signed-in visitor.

Loyalty

Membership & rewards

Points balance, status and benefits that change what's worth surfacing.

Commerce

Catalog & cart

Live stock, pricing, active promotions and the current cart or basket state.

Environment

Place & time

Geo, locale, weather or nearest-store inventory for context-aware framing.

Identity

Consented identity

A first-party identity graph that ties sessions together — with the visitor's consent.

A natural fit with Adobe Experience Platform. Because of1 is Adobe technology, connecting it to the Real-Time Customer Profile in Adobe Experience Platform / Real-Time CDP is a natural extension. An edge or server integration can resolve the visitor's profile — audiences, computed attributes and propensity scores — and pass it into the generation request, so the page reasons over the same unified profile the rest of your Adobe stack already uses. No built-in connector is required: any profile data that describes intent can be handed in on the request.

Still your data, still stateless. These richer sources are opt-in and first-party — supplied by you, with consent, as part of the request. of1 itself stays stateless: it reasons over whatever context it is handed for that one request and keeps nothing afterward. Any signal that helps describe intent can be plugged in.

What is stored

The service is stateless per request. It persists your content configuration — not visitor data.

Stateless by design. /api/generate does not log individual requests or responses to persistent storage. Behavioural analysis is computed per request and discarded. Multi-turn context works because the caller tracks and re-sends the behaviour profile — of1 keeps nothing between calls. Each tenant's configs, vectors and cache keys are fully isolated.

API endpoints

Public endpoints are CORS-enabled (Access-Control-Allow-Origin: *) and need no auth. Generation is rate-limited to 30 requests / 60s per IP. Admin endpoints require a token.

POST /api/generate

Generate a page — streams NDJSON. A minimal generation request:

POST /api/generate — example request

{

··"id": "frescopa--of1-demo--aem-growth-adoption",

··"query": "recommend a coffee machine for me",

··"browsing": [{ "url": "/espresso", "dwellTimeMs": 8200, "scrollDepth": 0.7 }],

··"segment": { "device": "mobile", "region": "ch" }

}

The response is a stream of NDJSON lines — section, suggestions, debug, done (default flow) or a single page event with full HTML + stylesheet (template-routing flow) — which the client consumes as it arrives. Per-request overrides such as ?device=mobile&region=fr&provider=bedrock&model=claude-opus are supported.

POST /api/suggest

Follow-up exploration prompts.

POST /api/personalize

Rewrite existing page elements per behaviour.

POST /api/tenants/:id/sync

Pull & index tenant config from EDS (admin).

Embed the SDK

The client SDK handles streaming, DOM injection and the debug widget so you don't have to.

Embed the of1 client SDK

<!-- 1. Load the SDK -->

<link rel="stylesheet" href="https://of1-gen-web-service.franklin-prod.workers.dev/sdk/of1-client.css">

<script src="https://of1-gen-web-service.franklin-prod.workers.dev/sdk/of1-client.js"></script>

// 2. Initialise against a container

OF1.init(document.querySelector('#of1'), {

··id: 'frescopa--of1-demo--aem-growth-adoption',

··query: userQuery,

});

Debug mode. Append ?debug=1 to any page using the SDK to reveal a widget showing the model used, timings, intent classification, RAG match counts and token usage — plus a runtime model-override control (stored in sessionStorage).

Tenant configuration

A tenant is your brand. Its content and behaviour are defined by JSON files authored in AEM / EDS and pulled into of1 via sync.

Publish these at https://<id>.aem.page/of1/config/<file>.json, then call POST /api/tenants/<id>/sync (with the admin token) to pull them into R2 and rebuild the vector index. Configs are cached in KV for 300 seconds.

Models & infrastructure

Model-agnostic. The reasoning engine is not tied to one provider. Providers are adapters — Cerebras, Workers AI and Bedrock ship today — and the model can be overridden per request for testing.

of1 · generative websites for an audience of one. This page documents OF1 Product — the runtime that produces personalized pages (of1-gen-web-service). © 2026 Adobe — early-stage technology by the Adobe Experience Manager team.