Analytics Agent — How It Works (Visual Guide)


The Problem Illustrated

Today: Fragmented Approaches

┌─────────────────────────────────────────────────────────────────┐
│                    CHAOS OF MULTIPLE PATHS                      │
├──────────────────────────────┬──────────────────────────────────┤
│ Team A (Search)              │ Team B (Cart)                    │
│ Uses satellite.track         │ Uses window.digitaldata mutation │
│                              │                                  │
│ analytics.track("event", {}) │ window.digitaldata.events = {}   │
│        ↓                      │        ↓                         │
│ Adobe Launch Rules           │ Adobe Launch Rules               │
│ (Team A custom logic)        │ (Team B custom logic)            │
│        ↓                      │        ↓                         │
│     (may conflict)           │     (may override)               │
└──────────────────────────────┴──────────────────────────────────┘
         ↓                              ↓
         └──────────────┬───────────────┘
                        ↓
                 Adobe Analytics
                        ↓
        "Why did event X break in ZA?"
        "Who owns this implementation?"
        "How do we add GA4 support?"

Result: Regressions, confusion, manual firefighting.


The Solution: App Builder + Thin Core SDK

┌─────────────────────────────────────────────────────────────────┐
│                 ALL PATHS THROUGH ONE DOOR                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                   │
│  Team A       Team B       Team C       Team D                   │
│  (Search)     (Cart)       (Checkout)   (Financing)              │
│      │            │              │            │                 │
│      └────────────┴──────────────┴────────────┘                 │
│                    │                                             │
│      App Analytics Builder (owned by feature team)              │
│                    │                                             │
│              analytics.track()                                   │
│                    │                                             │
│         ┌──────────┴──────────┐                                 │
│         ↓                     ↓                                 │
│    Validate          Route by Policy                            │
│    (Schema)          (satellite vs dataLayer)                   │
│         │                     │                                 │
│         └──────────┬──────────┘                                 │
│                    ↓                                             │
│         ┌──────────┴──────────┐                                 │
│         ↓                     ↓                                 │
│  satellite.track()   window.digitaldata                         │
│  (policy driven)     (policy driven)                            │
│         │                     │                                 │
│         └──────────┬──────────┘                                 │
│                    ↓                                             │
│            Adobe Launch                                         │
│            Adobe Analytics                                      │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

Result: App teams own business meaning. SDK stays small and stable.


Dependency Contract (Must Hold Forever)

  1. Shared analytics does not know app names, app routes, or app state models.
  2. Shared analytics accepts generic event envelopes only.
  3. Agent generates app-specific code in app folders only.
  4. Shared SDK changes only for core transport features, not feature business behavior.
  5. Shared SDK must not import app/domain TypeScript types; it processes runtime JSON envelopes only.

Agent Workflow (Step by Step)

You Provide a Simple Prompt

User Input:
──────────
"Add analytics for search filters in ZA and CA markets.
Events: apply, clear, reset.
Payload: filterId, filterValue, resultCount, market."

Step 1: Agent Parses Your Intent

Agent Internal Processing:
─────────────────────────
Intent Extraction:
  App: @nvc/search
  Events: ["FILTER_APPLIED", "FILTER_CLEARED", "FILTER_RESET"]
  Markets: ["za-en", "ca-en"]
  Base Markets: ["us-en"] (default)
  Payload Fields: {
    filterId: string,
    filterValue: string,
    resultCount: number,
    market: enum,
  }

Step 2: Agent Asks Clarifying Questions (If Needed)

Agent Asks:
──────────
1. Should these events fire on every interaction or 
   debounced (to avoid duplicate events)?
   
2. Should ZA have different field names or same as US?
   (e.g., "filterId" vs "filtro_id")

3. Which dispatch method for ZA?
   - satellite.track (current)
   - window.digitaldata injection (server-side)
   - both

You Answer:
──────────
1. Debounce 500ms
2. Same field names
3. satellite.track for ZA, dataLayer for CA

Step 3: Agent Generates Contracts

File: apps/@nvc/search/analytics/contracts/search-filters.ts

// Generated by agent from your prompt + answers
import { z } from "zod";

export const FILTER_APPLIED = {
  name: "SEARCH_FILTER_APPLIED",
  version: "1.0.0",
  schema: z.object({
    filterId: z.string().min(1),
    filterValue: z.string().min(1),
    resultCount: z.number().int().min(0),
    market: z.enum(["us-en", "za-en", "ca-en"]),
    timestamp: z.number(),
  }),
};

export const FILTER_CLEARED = { /* ... */ };
export const FILTER_RESET = { /* ... */ };

// Market-specific adjustments
export const MARKET_POLICY = {
  "za-en": {
    dispatchMethod: "satellite.track",
    requiredFields: ["filterId", "filterValue", "market"],
  },
  "ca-en": {
    dispatchMethod: "dataLayer",
    requiredFields: ["filterId", "filterValue", "market"],
  },
};

Versioning behavior:

  1. Agent computes next version based on diff type.
  2. Additive field change -> MINOR bump.
  3. Required field removal/rename -> MAJOR bump and migration note.
  4. Non-behavioral update -> PATCH bump.

Step 4: Agent Generates App-Level Builder Code

File: apps/@nvc/search/components/filterPanel.tsx

// Generated by agent from contract
import { analytics } from "@analytics/sdk";
import {
  FILTER_APPLIED,
  FILTER_CLEARED,
  FILTER_RESET,
} from "../analytics/contracts/search-filters";

let lastFilterEventTime = 0;
const DEBOUNCE_MS = 500;

export function FilterPanel({ market }) {
  const fireEvent = (eventName, eventData) => {
    const now = Date.now();
    if (now - lastFilterEventTime < DEBOUNCE_MS) return;
    lastFilterEventTime = now;

    // App owns business mapping and payload creation
    // SDK only validates, routes, dispatches
    analytics.track(eventName, {
      ...eventData,
      market,
      timestamp: now,
    });
  };

  const handleApply = (filter) => {
    fireEvent(FILTER_APPLIED.name, {
      filterId: filter.id,
      filterValue: filter.value,
      resultCount: getResultCount(),
    });
  };

  const handleClear = () => {
    fireEvent(FILTER_CLEARED.name, {
      filterId: "all",
      filterValue: "none",
      resultCount: getResultCount(),
    });
  };

  const handleReset = () => {
    fireEvent(FILTER_RESET.name, {
      filterId: "reset",
      filterValue: "none",
      resultCount: getResultCount(),
    });
  };

  return (
    <div>
      <button data-removed={handleApply}>Apply Filter</button>
      <button data-removed={handleClear}>Clear</button>
      <button data-removed={handleReset}>Reset</button>
    </div>
  );
}

Step 5: Agent Generates Tests

File: apps/@nvc/search/__tests__/filterPanel.analytics.test.tsx

// Generated by agent
import { render, fireEvent } from "@testing-library/react";
import { analytics } from "@analytics/sdk";
import {
  FILTER_APPLIED,
  FILTER_CLEARED,
} from "../../analytics/contracts/search-filters";
import { FilterPanel } from "../filterPanel";

jest.mock("@analytics/sdk");

describe("FilterPanel Analytics", () => {
  describe("Positive Scenarios", () => {
    it("should track FILTER_APPLIED event with correct payload", () => {
      const { getByText } = render(<FilterPanel market="za-en" />);
      fireEvent.click(getByText("Apply Filter"));

      expect(analytics.track).toHaveBeenCalledWith(
        FILTER_APPLIED.name,
        expect.objectContaining({
          filterId: expect.any(String),
          filterValue: expect.any(String),
          resultCount: expect.any(Number),
          market: "za-en",
          timestamp: expect.any(Number),
        })
      );
    });

    it("should track FILTER_CLEARED event", () => {
      const { getByText } = render(<FilterPanel market="ca-en" />);
      fireEvent.click(getByText("Clear"));

      expect(analytics.track).toHaveBeenCalledWith(
        FILTER_CLEARED.name,
        expect.any(Object)
      );
    });

    it("should respect debounce and fire only once", () => {
      const { getByText } = render(<FilterPanel market="us-en" />);
      fireEvent.click(getByText("Apply Filter"));
      fireEvent.click(getByText("Apply Filter"));
      fireEvent.click(getByText("Apply Filter"));

      // Only first click fires event
      expect(analytics.track).toHaveBeenCalledTimes(1);
    });
  });

  describe("Negative Scenarios", () => {
    it("should handle missing required fields gracefully", () => {
      // Test that invalid payload is rejected
      expect(() => {
        analytics.track(FILTER_APPLIED.name, {
          filterId: "",
          filterValue: "",
          resultCount: -1,
          market: "za-en",
        });
      }).toThrow();
    });

    it("should reject unknown market", () => {
      expect(() => {
        analytics.track(FILTER_APPLIED.name, {
          filterId: "123",
          filterValue: "sedan",
          resultCount: 45,
          market: "xx-yy", // invalid
        });
      }).toThrow();
    });
  });
});

Step 6: Agent Generates E2E Test (Analytics Payload Snapshot)

File: apps/@nvc/search-e2e/tests/analytics/search-filters.e2e.ts

// Generated by agent
import { test, expect } from "@playwright/test";
import type { SearchResultsPage } from "../../pageObjects/searchResultsPage";

test.describe("Search Filter Analytics", () => {
  let expectedPayloads: Record<string, any> = {};

  test("should emit correct payloads for filter events", async ({ page }) => {
    // Intercept analytics calls
    const capturedEvents: any[] = [];

    page.on("console", (msg) => {
      if (msg.type() === "log" && msg.text().includes("ANALYTICS_EVENT")) {
        capturedEvents.push(JSON.parse(msg.text()));
      }
    });

    // Navigate and interact
    await page.goto("/search?market=za-en");
    const searchPage = new SearchResultsPage(page);

    // Apply filter
    await searchPage.applyFilter("type", "sedan");
    await page.waitForTimeout(1000);

    // Verify payload
    expect(capturedEvents).toContainEqual(
      expect.objectContaining({
        eventName: "SEARCH_FILTER_APPLIED",
        payload: expect.objectContaining({
          filterId: "type",
          filterValue: "sedan",
          market: "za-en",
        }),
      })
    );

    // Snapshot comparison
    expect(capturedEvents).toMatchSnapshot(
      "filter-events-za-en-snapshot.json"
    );
  });
});

Step 7: Agent Updates Policy Config

File: packages/@analytics/config/dispatch-policy.json

{
  "SEARCH_FILTER_APPLIED": {
    "default": "satellite.track",
    "za-en": "satellite.track",
    "ca-en": "dataLayer"
  },
  "SEARCH_FILTER_CLEARED": {
    "default": "satellite.track",
    "za-en": "satellite.track",
    "ca-en": "dataLayer"
  },
  "SEARCH_FILTER_RESET": {
    "default": "satellite.track",
    "za-en": "satellite.track",
    "ca-en": "dataLayer"
  }
}

Step 8: Agent Opens PR with Evidence

PR Title:
────────
feat(analytics): add search filter tracking for ZA and CA markets

PR Description:
───────────────
## What Changed
- Added 3 event contracts (FILTER_APPLIED, FILTER_CLEARED, FILTER_RESET)
- Generated SDK integration in @nvc/search
- Added market-specific dispatch policies
- Generated unit tests + E2E analytics snapshot tests

## Events Added
- SEARCH_FILTER_APPLIED (v1.0.0)
- SEARCH_FILTER_CLEARED (v1.0.0)
- SEARCH_FILTER_RESET (v1.0.0)

## Markets Supported
- za-en (dispatch: satellite.track)
- ca-en (dispatch: dataLayer)
- us-en (dispatch: satellite.track, default)

## Quality Evidence
✓ All contracts pass schema validation
✓ Unit tests: 12 passed
✓ E2E analytics snapshot test: passed
✓ Type safety: no errors
✓ Lint: no issues
✓ Version compatibility check: passed
✓ Canary plan: attached

## Rollback Plan
If needed, revert commit + remove event definitions from catalog

## Test Evidence Attached
- Unit test output
- E2E snapshot comparison
- Payload samples for each market

Files Changed:
  apps/@nvc/search/analytics/contracts/search-filters.ts
  packages/@analytics/config/dispatch-policy.json
  apps/@nvc/search/components/filterPanel.tsx
  apps/@nvc/search/__tests__/filterPanel.analytics.test.tsx
  apps/@nvc/search-e2e/tests/analytics/search-filters.e2e.ts

Fail-Safe Release Flow

[Diagram]

Step 9: You Review and Approve

Your Review (2 minutes):
──────────────────────
✓ Check contract matches business requirement
✓ Verify markets are correct
✓ Scan code for any manual mistakes
✓ Approve

Step 10: Auto-Merge and Deploy

System:
──────
✓ Merge PR
✓ Deploy to dev
✓ Run production monitoring
✓ Payload quality dashboard updates
✓ Drift detection armed

Agent Capabilities Maturity Levels

Level 1: Assisted (Week 4)

Level 2: Autonomous PR (Week 8)

Level 3: Controlled Auto-Merge (Week 12)

Level 4: Full Intent-to-Production (Week 16+)


Visual: Before and After

Before (Today)

Day 1: Read PV doc (30 min)
Day 2: Plan implementation (1 hour)
Day 3: Code in 3-4 places (2 hours)
Day 4: Write tests (2 hours)
Day 5: Debug cross-market issues (2 hours)
Day 6: Review and merge (1 hour)

Total: 4-5 days per event

After (With Agent)

Minute 1: Give prompt
Minute 2-5: Answer 2-3 questions
Minute 6-10: Review PR
Minute 11: Approve

Total: 10 minutes per event

Reduction: 95% less time and manual coding.


What the Agent Handles So You Don't Have To

  1. ✓ Reading and interpreting requirements
  2. ✓ Writing contracts and schemas
  3. ✓ Generating app-level analytics builder code
  4. ✓ Handling market/brand overlays
  5. ✓ Writing unit tests
  6. ✓ Writing E2E payload snapshots
  7. ✓ Updating policy configs
  8. ✓ Running all validations
  9. ✓ Opening PRs with evidence
  10. ✓ Detecting regressions in production

What You Still Own

  1. ✓ Business requirements interpretation
  2. ✓ App-level event semantics (if you want custom behavior)
  3. ✓ PR review (2 min scan)
  4. ✓ Final approval
  5. ✓ Release decision
  6. ✓ Production monitoring alerts

Summary

Your workflow:

  1. Give simple prompt
  2. Ask/answer 2-3 questions
  3. Review PR (looks good?)
  4. Approve
  5. Done

Agent does: Everything else.

Result: No manual analytics coding ever again.