Skip to content

mvrzan/salesforce-agentforce-march-madness

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

195 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Agentforce Data360 Heroku

March Madness with Agentforce, Data360, and Heroku

A full-stack NCAA March Madness bracket builder where you compete head-to-head against a Salesforce Agentforce AI agent that streams its predictions and reasoning in real time using live ESPN data.

Note: This project requires a Salesforce org with Agentforce enabled and a Heroku app with the Heroku AppLink add-on attached.


Table of Contents


What does it do?

This application lets users build their own March Madness bracket and then pits it against an AI agent powered by Salesforce Agentforce. The AI streams its picks and reasoning round by round — you can watch it think in real time. Once the tournament is underway, the app pulls live scores from ESPN so the AI can adapt its remaining predictions based on actual results.

The demo showcases two operational modes:

Human Bracket Builder — A manual bracket where you select winners through all 6 rounds:

  • Full 64-team tournament bracket across 4 regions (East, West, South, Midwest)
  • Pick winners for every matchup from the Round of 64 through the Championship
  • Picks are auto-saved and persisted across page refreshes
  • Live ESPN data feeds bracket seedings and scores in real time

AI Bracket Generation — Agentforce generates a complete bracket via streaming:

  • 8 sequential prompts sent to the Agentforce agent (one per region for Round of 64, then Round of 32, Sweet 16, Elite 8, Final Four + Championship)
  • Picks stream back in real time via Server-Sent Events (SSE)
  • The AI's full reasoning is rendered as markdown in a side panel
  • If the agent misses any matchups, a targeted retry regenerates only the missing picks

Key capabilities:

  • Live ESPN Integration: Tournament data, seedings, and live scores pulled directly from the ESPN API with in-process TTL caching
  • Streaming AI Predictions: Agentforce responses stream token-by-token via SSE — picks are extracted on-the-fly using regex pattern matching
  • Adaptive AI: After real tournament rounds complete, the AI reviews the results and adjusts its remaining predictions accordingly via POST /api/v1/af/bracket/adapt
  • Head-to-Head Scoring: Compare your bracket against the AI's bracket scored against actual ESPN results using exponential round points (1 → 2 → 4 → 8 → 16 → 32, max 192 points)
  • Persistent State: User picks and AI reasoning survive page navigation and refreshes via localStorage — no backend persistence required
  • Heroku AppLink Auth: Salesforce OAuth tokens are retrieved securely via the Heroku AppLink SDK — no credentials stored in environment variables

How does it work?

AI Bracket Generation Flow:

  1. Session Init: The frontend generates a UUID session ID and calls POST /api/v1/af/sessions/:sessionId to open an Agentforce session
  2. Auth via AppLink: The backend uses the @heroku/applink SDK to retrieve a Salesforce OAuth access token and instance URL at request time
  3. Round Prompt: The frontend calls POST /api/v1/af/bracket/round with the current round and region context
  4. Agent Call: The backend constructs a prompt with the full matchup list and forwards it to the Agentforce Sessions API (/einstein/ai-agent/v1/sessions/{id}/messages/stream)
  5. SSE Streaming: Agentforce streams the response chunk-by-chunk; the backend proxies this stream directly to the frontend via SSE
  6. Pick Extraction: The frontend's useSSE hook accumulates text chunks and extracts picks in real time using the pattern PICK: [matchupId] -> [winnerId]
  7. Retry Logic: After each round, any matchups the agent missed are collected and a targeted POST /api/v1/af/bracket/retry re-prompts the agent for only those picks
  8. Repeat: Steps 3–7 repeat for all 8 prompts until the full bracket is complete
  9. Session Cleanup: On page unmount, DELETE /api/v1/af/delete-session closes the Agentforce session

Manual Bracket + Live Scoring Flow:

  1. Bracket Load: The frontend fetches the full bracket structure from GET /api/v1/agentforce/results/bracket, which queries the ESPN Scoreboard API and maps games to the internal tournament model
  2. ESPN Cache: The ESPN service caches bracket data for 5 minutes and live scores for 30 seconds to avoid hammering the API on low-RAM Heroku dynos
  3. User Picks: As users select winners, picks are saved locally to localStorage and persist across page refreshes
  4. Live Polling: The useLivePolling hook polls GET /api/v1/agentforce/results/live every N seconds to refresh scores for in-progress games
  5. Adaptive AI: On the Live page, the AI can review completed round results and call POST /api/v1/af/bracket/adapt with actual outcomes to adjust remaining picks
  6. Scoring: The Compare page scores both brackets locally against real ESPN results — no additional server call needed

Demo

Architecture Diagram

Architecture Diagram

Home Page

Home Page

Manual Bracket Builder

Bracket Builder

AI Bracket Generation (Streaming)

AI Bracket Generation

Live Scores

Live Scores

Head-to-Head Comparison

Compare Page


API Specification

Authentication

All Agentforce-facing routes authenticate via Heroku AppLink. The backend SDK (@heroku/applink) retrieves a short-lived Salesforce OAuth access token at request time using the APP_LINK_CONNECTION_NAME attachment. No token is stored in environment variables.

Inbound requests from Salesforce (tool invocations on agentforceTools routes) are validated using HMAC signature verification against the API_SECRET shared secret. Invalid signatures return 401 Unauthorized.

Middleware

Middleware Applies To Purpose
cors All routes Restricts browser requests to the origin set in CLIENT_ORIGIN
express.json All routes Parses JSON request bodies
validateSignature agentforceApiRoutes + agentforceTools routes HMAC-validates inbound requests from the client and Salesforce agent tool calls
herokuServiceMesh Selected routes Heroku service mesh integration
errorHandler All routes (last) Centralized JSON error responses

Agentforce Session Routes

POST /api/v1/af/sessions/:sessionId

  • Auth required: Yes (AppLink)
  • Description: Opens a new Agentforce agent session with the provided external session key
  • URL params: sessionId — UUID identifying the client session
  • Response: 200 { sessionId, messages }
  • Error responses: 500 if AppLink auth or Agentforce API fails

DELETE /api/v1/af/delete-session

  • Auth required: Yes (AppLink)
  • Description: Closes an active Agentforce session
  • Request body: { sessionId: string }
  • Response: 200 { message }
  • Error responses: 500 if session deletion fails

Agentforce Bracket Routes

POST /api/v1/af/bracket/round

  • Auth required: Yes (HMAC signature)
  • Description: Generates AI bracket picks for a specific round by prompting the Agentforce agent; streams picks back via SSE
  • Request body: { sessionId: string, sequenceId: number, roundIndex: number, priorPicks: Record<string, string> }
  • Response: text/event-stream
  • Error responses: 400 if required fields are missing, 500 on agent error

POST /api/v1/af/bracket/retry

  • Auth required: Yes (HMAC signature)
  • Description: Retries AI pick generation for specific matchups the agent previously missed
  • Request body: { sessionId: string, sequenceId: number, missingMatchupIds: string[], priorPicks: Record<string, string> }
  • Response: text/event-stream
  • Error responses: 400, 500

POST /api/v1/af/bracket/adapt

  • Auth required: Yes (HMAC signature)
  • Description: Prompts the Agentforce agent to review actual completed-round results and adapt its remaining bracket predictions; the full prompt is built server-side to prevent prompt injection
  • Request body: { sessionId: string, sequenceId: number, round: string, completedMatchups: Matchup[], aiBracket: Bracket | null }
  • Response: text/event-stream
  • Error responses: 400, 500

Results Routes

GET /api/v1/agentforce/results/teams

  • Auth required: No
  • Description: Returns all 64 tournament teams with seedings and regions
  • Response: 200 { teams: Team[] }

GET /api/v1/agentforce/results/bracket

  • Auth required: No
  • Description: Returns the full bracket structure from ESPN (cached 5 min) or falls back to static 2025 data
  • Response: 200 { bracket: Bracket, isLive: boolean }

GET /api/v1/agentforce/results/live

  • Auth required: No
  • Description: Returns live game scores and statuses from ESPN (cached 30 sec)
  • Response: 200 { games: LiveGame[] }

Tech Stack

Layer Technology Description
Frontend React 19, Vite, Tailwind CSS 4, React Router 7 UI library, build tool, utility-first CSS, client-side routing
Frontend React Markdown, Lucide React, UUID Markdown rendering for AI reasoning, icons, session ID generation
Backend Node.js, Express 5, TypeScript 5 JavaScript runtime, web framework, static typing
AI / LLM Salesforce Agentforce, Salesforce Data360 Autonomous AI agent platform, data cloud backend for agent knowledge
Auth Heroku AppLink Secure Salesforce OAuth token retrieval without storing credentials
Data ESPN API Real-time tournament bracket, scores, and game statuses
Hosting Heroku Cloud platform for both frontend static assets and backend API

Configuration

Prerequisites

To run this application locally, you will need the following:

  • Node.js v22 or later installed (run node -v to check). Follow the Node.js install instructions if needed
  • npm v10 or later installed (run npm -v to check). Node.js includes npm
  • git installed. Follow the instructions to install git
  • A Salesforce org with Agentforce enabled and an agent deployed
  • A Heroku account with an app that has the Heroku AppLink add-on attached and connected to your Salesforce org
  • (Optional) A Salesforce Data360 account if your agent uses Data Cloud as a knowledge source

Local Environment Configuration

  1. Clone the repository

    git clone https://github.com/your-org/salesforce-agentforce-march-madness.git
    cd salesforce-agentforce-march-madness
  2. Configure Salesforce Agentforce

    Important: You must have a Salesforce org with Agentforce enabled before proceeding.

    Follow the Agentforce documentation to:

    • Create and deploy an Agentforce agent in your Salesforce org
    • Note the Agent ID from the agent's detail page — you will need it for AGENTFORCE_AGENT_ID
  3. Configure Heroku AppLink

    Important: Heroku AppLink is required for the backend to authenticate with Salesforce. It does not work with a plain .env file in production.

    Follow the Heroku AppLink setup guide to:

    • Attach the AppLink add-on to your Heroku app
    • Connect the add-on to your Salesforce org
    • Note the connection name — this is your APP_LINK_CONNECTION_NAME value
  4. Configure environment variables

    Server:

    cp server/src/.env.example server/src/.env

    Open server/src/.env and fill in all required values (see Environment Variables).

    Client:

    cp client/.env.example client/.env

    Open client/.env and fill in all required values.

    Generate a shared secret for signature validation if needed:

    node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

    ⚠️ Note: API_SECRET in server/src/.env and VITE_API_SECRET in client/.env must match exactly.

  5. Install dependencies

    Install all workspace dependencies from the root:

    # Server
    cd server && npm install && cd ..
    
    # Client
    cd client && npm install && cd ..
  6. Start the application

    Open two terminals:

    # Terminal 1 — backend
    cd server
    npm run dev
    # Terminal 2 — frontend
    cd client
    npm run dev
  7. Access the application

    Once both servers are running, open your browser and navigate to http://localhost:5173.

Environment Variables

Server (server/src/.env)

Variable Required Description
APP_PORT No Port the Express server listens on locally. Defaults to 3000
CLIENT_ORIGIN No CORS-allowed origin for the frontend. Defaults to http://localhost:5173
APP_LINK_CONNECTION_NAME Yes Heroku AppLink connection name used to retrieve Salesforce OAuth tokens
AGENTFORCE_AGENT_ID Yes Salesforce Agentforce Agent ID from your org's agent configuration page
API_SECRET Yes Shared HMAC secret used to validate inbound Salesforce agent tool requests

Client (client/.env)

Variable Required Description
VITE_API_BASE No Base URL for the backend API. Defaults to http://localhost:3000
VITE_API_SECRET Yes Must match API_SECRET on the server for signature validation

Salesforce Agentforce Configuration

Agent Setup

Your Agentforce agent must be:

  • Published and active in your Salesforce org
  • Accessible by the AppLink-connected user profile
  • Configured with appropriate knowledge sources (Data360 recommended for team statistics and historical NCAA data)
  • Ensure your Agentforce Actions are using the Heroku AppLink as described here.

Agent Tools

The server exposes tool endpoints under agentforceTools routes that Salesforce agents can call back into the application. These endpoints are protected by HMAC signature validation using the shared API_SECRET.

Agent Details

Field Value
Name March Madness Bracket Analyst
Description This is the March Madness Bracket Analyst agent that analyzes the scores and provides suggestions accordingly.
Role You are a March Madness Bracket Analyst powered by Salesforce Agentforce. Your job is to analyze the NCAA Men's Basketball Tournament field, generate data-driven bracket predictions, and adapt your picks in real time as the tournament unfolds.
Company This is just a demo Agent that analyzes the scores of march madness and provides suggestions accordingly.

Topic: Bracket Generation

Field Value
Name Bracket Generation
Classification Description Analyzing the full 64-team field and predicting winners through all 6 rounds of the tournament.
Scope My job is to analyze the 64-team field, evaluate matchups, and predict winners for each of the 6 rounds in the tournament. I will not handle tasks outside of bracket analysis or predictions.

Agent Instructions

  1. Always explain your reasoning for each pick citing seed, record, region strength, and historical tournament performance.

  2. When making bracket picks, output each pick on its own line in this exact format: PICK: MATCHUP_ID -> TEAM_ABBREVIATION followed by REASON: One sentence. Use the exact MATCHUP_ID and TEAM_ABBREVIATION values from the bracket data. Do not use markdown bold, asterisks, backticks, or brackets around pick values.

  3. Work through regions in order: East → West → South → Midwest → Final Four → Championship.

  4. Flag upset picks explicitly with UPSET ALERT: when picking a seed 5 or lower over a seed 4 or higher.

  5. When providing bracket picks, you MUST follow this exact format for every single pick — no exceptions:

    PICK: [MATCHUP_ID] -> [TEAM_ABBREVIATION]
    REASON: [One sentence explanation]
    

    Rules:

    • Use ONLY the keyword PICK: — never UPSET ALERT:, PREDICTION:, or any other prefix
    • MATCHUP_ID must be exactly as returned by the Get Bracket Structure action (e.g. East-R64-1v16, Midwest-Roundof32-1, FF-1, CHAMP-1)
    • TEAM_ABBREVIATION must be exactly as returned by the Get Bracket Structure action (e.g. DUKE, HOU, GONZ)
    • One PICK line per matchup, on its own line
    • Do not use markdown bold (**), bullet points, or any other formatting on the PICK lines themselves
    • You may use headers and prose between PICK blocks, but each PICK line must be clean and unformatted

    Example:

    PICK: East-R64-1v16 -> DUKE
    REASON: Duke dominates as a #1 seed against Mount St. Mary's.
    PICK: Midwest-Roundof32-1 -> HOU
    REASON: Houston advances on balanced scoring and elite defense.
    
  6. You are making PREDICTIONS for an entire bracket before any games are played. You MUST pick every single matchup in every round — Round of 64 through Championship — regardless of whether results exist. For rounds beyond Round of 64, determine the expected participants by picking the winner of each prior matchup based on seeding, historical performance, and your own analysis. Then make a pick for that matchup. Never refuse to pick a matchup because results are unknown. That is the entire point — you are predicting, not reporting. If no winner has been recorded, project one yourself and continue.


Deployment

This project is deployed on Heroku. The root package.json start script handles the full build and launch sequence — Heroku runs it automatically on each deploy.

1. Set Configuration Variables

Set each server environment variable as a Heroku config var:

heroku config:set APP_LINK_CONNECTION_NAME=your_connection_name
heroku config:set AGENTFORCE_AGENT_ID=your_agent_id
heroku config:set API_SECRET=your_api_secret
heroku config:set CLIENT_ORIGIN=https://your-app-name.herokuapp.com

PORT is set automatically by Heroku — do not override it.

2. Deploy

git push heroku main

Heroku runs npm install and then npm start from the root automatically. The start script builds the React client, moves the compiled assets into server/public, and starts the Express server. The Express server then serves both the API and the React SPA.


License

MIT


Disclaimer

This software is to be considered "sample code", a Type B Deliverable, and is delivered "as-is" to the user. Salesforce bears no responsibility to support the use or implementation of this software.

About

A full-stack NCAA March Madness bracket builder where you compete head-to-head against a Salesforce Agentforce AI agent that streams its predictions and reasoning in real time using live ESPN data.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages