diff --git a/.env.test b/.env.test
index 420a16d8..bc876bcf 100644
--- a/.env.test
+++ b/.env.test
@@ -19,3 +19,4 @@ SITE_NAME=localhost
 USER_INFO_COOKIE_NAME='edx-user-info'
 APP_ID=''
 MFE_CONFIG_API_URL=''
+OPTIMIZELY_FULL_STACK_SDK_KEY='test-optimizely-sdk-full-stack-key'
diff --git a/module.config.js.example b/module.config.js.example
index c16d1186..00143e7f 100644
--- a/module.config.js.example
+++ b/module.config.js.example
@@ -27,5 +27,6 @@ module.exports = {
     // { moduleName: '@openedx/paragon/icons', dir: '../paragon', dist: 'icons' },
     // { moduleName: '@openedx/paragon', dir: '../paragon', dist: 'dist' },
     // { moduleName: '@edx/frontend-platform', dir: '../frontend-platform', dist: 'dist' },
+    // { moduleName: '@optimizely/react-sdk', dir: '../src/frontend-lib-learning-assistant/src/mocks/optimizely', dist: '.' },
   ],
 };
diff --git a/src/components/MessageForm/index.jsx b/src/components/MessageForm/index.jsx
index 4f2cb163..b1ec12c6 100644
--- a/src/components/MessageForm/index.jsx
+++ b/src/components/MessageForm/index.jsx
@@ -1,10 +1,12 @@
 import PropTypes from 'prop-types';
 import React, { useEffect, useRef } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
-
+import { useDecision } from '@optimizely/react-sdk';
 import { Button, Form, Icon } from '@openedx/paragon';
 import { Send } from '@openedx/paragon/icons';
+import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
 
+import { OPTIMIZELY_PROMPT_EXPERIMENT_KEY } from '../../data/optimizely';
 import {
   acknowledgeDisclosure,
   addChatMessage,
@@ -17,6 +19,11 @@ const MessageForm = ({ courseId, shouldAutofocus, unitId }) => {
   const dispatch = useDispatch();
   const inputRef = useRef();
 
+  const { userId } = getAuthenticatedUser();
+  const [decision] = useDecision(OPTIMIZELY_PROMPT_EXPERIMENT_KEY, { autoUpdate: true }, { id: userId.toString() });
+  const { active, variationKey } = decision || {};
+  const promptExperimentVariationKey = active ? variationKey : undefined;
+
   useEffect(() => {
     if (inputRef.current && !apiError && !apiIsLoading && shouldAutofocus) {
       inputRef.current.focus();
@@ -25,10 +32,11 @@ const MessageForm = ({ courseId, shouldAutofocus, unitId }) => {
 
   const handleSubmitMessage = (event) => {
     event.preventDefault();
+
     if (currentMessage) {
       dispatch(acknowledgeDisclosure(true));
-      dispatch(addChatMessage('user', currentMessage, courseId));
-      dispatch(getChatResponse(courseId, unitId));
+      dispatch(addChatMessage('user', currentMessage, courseId, promptExperimentVariationKey));
+      dispatch(getChatResponse(courseId, unitId, promptExperimentVariationKey));
     }
   };
 
@@ -43,13 +51,14 @@ const MessageForm = ({ courseId, shouldAutofocus, unitId }) => {
       onClick={handleSubmitMessage}
       size="inline"
       variant="tertiary"
+      data-testid="message-form-submit"
     >
       <Icon src={Send} />
     </Button>
   );
 
   return (
-    <Form className="w-100 pl-2" onSubmit={handleSubmitMessage}>
+    <Form className="w-100 pl-2" onSubmit={handleSubmitMessage} data-testid="message-form">
       <Form.Group>
         <Form.Control
           data-hj-suppress
diff --git a/src/components/MessageForm/index.test.jsx b/src/components/MessageForm/index.test.jsx
new file mode 100644
index 00000000..61929f3f
--- /dev/null
+++ b/src/components/MessageForm/index.test.jsx
@@ -0,0 +1,195 @@
+import React from 'react';
+import {
+  screen, act, fireEvent, waitFor,
+} from '@testing-library/react';
+import { useDecision } from '@optimizely/react-sdk';
+import { render as renderComponent } from '../../utils/utils.test';
+import { initialState } from '../../data/slice';
+import { OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS } from '../../data/optimizely';
+import {
+  acknowledgeDisclosure,
+  addChatMessage,
+  getChatResponse,
+  updateCurrentMessage,
+} from '../../data/thunks';
+
+import MessageForm from '.';
+
+jest.mock('../../utils/surveyMonkey', () => ({
+  showControlSurvey: jest.fn(),
+  showVariationSurvey: jest.fn(),
+}));
+
+jest.mock('@edx/frontend-platform/analytics', () => ({
+  sendTrackEvent: jest.fn(),
+}));
+
+const mockedAuthenticatedUser = { userId: 123 };
+jest.mock('@edx/frontend-platform/auth', () => ({
+  getAuthenticatedUser: () => mockedAuthenticatedUser,
+}));
+
+jest.mock('@edx/frontend-platform/analytics', () => ({
+  sendTrackEvent: jest.fn(),
+}));
+
+const mockDispatch = jest.fn();
+jest.mock('react-redux', () => ({
+  ...jest.requireActual('react-redux'),
+  useDispatch: () => mockDispatch,
+}));
+
+jest.mock('@optimizely/react-sdk', () => ({
+  useDecision: jest.fn(),
+}));
+
+jest.mock('../../data/thunks', () => ({
+  acknowledgeDisclosure: jest.fn(),
+  addChatMessage: jest.fn(),
+  getChatResponse: jest.fn(),
+  updateCurrentMessage: jest.fn(),
+}));
+
+const defaultProps = {
+  courseId: 'some-course-id',
+  isOpen: true,
+  setIsOpen: jest.fn(),
+  unitId: 'some-unit-id',
+};
+
+const render = async (props = {}, sliceState = {}) => {
+  const componentProps = {
+    ...defaultProps,
+    ...props,
+  };
+
+  const initState = {
+    preloadedState: {
+      learningAssistant: {
+        ...initialState,
+        ...sliceState,
+      },
+    },
+  };
+  return act(async () => renderComponent(
+    <MessageForm {...componentProps} />,
+    initState,
+  ));
+};
+
+describe('<MessageForm />', () => {
+  beforeEach(() => {
+    jest.resetAllMocks();
+    useDecision.mockReturnValue([]);
+  });
+
+  describe('when rendered', () => {
+    it('should focus if shouldAutofocus is enabled', () => {
+      const currentMessage = 'How much wood';
+      const sliceState = {
+        apiIsLoading: false,
+        currentMessage,
+        apiError: false,
+      };
+
+      render({ shouldAutofocus: true }, sliceState);
+
+      waitFor(() => {
+        expect(screen.getByDisplayValue(currentMessage)).toHaveFocus();
+      });
+
+      expect(screen.queryByTestId('message-form')).toBeInTheDocument();
+    });
+
+    it('should dispatch updateCurrentMessage() when updating the form control', () => {
+      const currentMessage = 'How much wood';
+      const updatedMessage = 'How much wood coud a woodchuck chuck';
+      const sliceState = {
+        apiIsLoading: false,
+        currentMessage,
+        apiError: false,
+      };
+
+      render(undefined, sliceState);
+
+      act(() => {
+        const input = screen.getByDisplayValue(currentMessage);
+        fireEvent.change(input, { target: { value: updatedMessage } });
+      });
+
+      expect(updateCurrentMessage).toHaveBeenCalledWith(updatedMessage);
+      expect(mockDispatch).toHaveBeenCalledTimes(1);
+    });
+
+    it('should dispatch message on submit as expected', () => {
+      const currentMessage = 'How much wood could a woodchuck chuck if a woodchuck could chuck wood?';
+      const sliceState = {
+        apiIsLoading: false,
+        currentMessage,
+        apiError: false,
+      };
+
+      render(undefined, sliceState);
+
+      act(() => {
+        screen.queryByTestId('message-form-submit').click();
+      });
+
+      expect(acknowledgeDisclosure).toHaveBeenCalledWith(true);
+      expect(addChatMessage).toHaveBeenCalledWith('user', currentMessage, defaultProps.courseId, undefined);
+      expect(getChatResponse).toHaveBeenCalledWith(defaultProps.courseId, defaultProps.unitId, undefined);
+      expect(mockDispatch).toHaveBeenCalledTimes(3);
+    });
+
+    it('should not dispatch on submit if there\'s no message', () => {
+      render();
+
+      act(() => {
+        screen.queryByTestId('message-form-submit').click();
+      });
+
+      expect(acknowledgeDisclosure).not.toHaveBeenCalled();
+      expect(addChatMessage).not.toHaveBeenCalled();
+      expect(getChatResponse).not.toHaveBeenCalled();
+      expect(mockDispatch).not.toHaveBeenCalled();
+    });
+  });
+
+  describe('prmpt experiment', () => {
+    beforeEach(() => {
+      useDecision.mockReturnValue([{
+        active: true,
+        variationKey: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
+      }]);
+    });
+
+    it('should include experiment data on submit', () => {
+      const currentMessage = 'How much wood could a woodchuck chuck if a woodchuck could chuck wood?';
+      const sliceState = {
+        apiIsLoading: false,
+        currentMessage,
+        apiError: false,
+      };
+
+      render(undefined, sliceState);
+
+      act(() => {
+        screen.queryByTestId('message-form-submit').click();
+      });
+
+      expect(acknowledgeDisclosure).toHaveBeenCalledWith(true);
+      expect(addChatMessage).toHaveBeenCalledWith(
+        'user',
+        currentMessage,
+        defaultProps.courseId,
+        OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
+      );
+      expect(getChatResponse).toHaveBeenCalledWith(
+        defaultProps.courseId,
+        defaultProps.unitId,
+        OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
+      );
+      expect(mockDispatch).toHaveBeenCalledTimes(3);
+    });
+  });
+});
diff --git a/src/components/Sidebar/index.jsx b/src/components/Sidebar/index.jsx
index 8d0b7312..f888156a 100644
--- a/src/components/Sidebar/index.jsx
+++ b/src/components/Sidebar/index.jsx
@@ -1,7 +1,9 @@
 import React, { useRef, useEffect } from 'react';
 import { useDispatch, useSelector } from 'react-redux';
 import PropTypes from 'prop-types';
+import { useDecision } from '@optimizely/react-sdk';
 import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
 import {
   Button,
   Icon,
@@ -10,7 +12,7 @@ import {
 import { Close } from '@openedx/paragon/icons';
 
 import { clearMessages } from '../../data/thunks';
-import { PROMPT_EXPERIMENT_FLAG, PROMPT_EXPERIMENT_KEY } from '../../constants/experiments';
+import { OPTIMIZELY_PROMPT_EXPERIMENT_KEY, OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS } from '../../data/optimizely';
 import { showControlSurvey, showVariationSurvey } from '../../utils/surveyMonkey';
 
 import APIError from '../APIError';
@@ -29,12 +31,18 @@ const Sidebar = ({
     apiError,
     disclosureAcknowledged,
     messageList,
-    experiments,
   } = useSelector(state => state.learningAssistant);
-  const { variationKey } = experiments?.[PROMPT_EXPERIMENT_FLAG] || {};
   const chatboxContainerRef = useRef(null);
   const dispatch = useDispatch();
 
+  const { userId } = getAuthenticatedUser();
+  const [decision] = useDecision(OPTIMIZELY_PROMPT_EXPERIMENT_KEY, { autoUpdate: true }, { id: userId.toString() });
+  const { active: activeExperiment, variationKey } = decision || {};
+  const experimentPayload = activeExperiment ? {
+    experiment_name: OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+    variation_key: variationKey,
+  } : {};
+
   // this use effect is intended to scroll to the bottom of the chat window, in the case
   // that a message is larger than the chat window height.
   useEffect(() => {
@@ -73,7 +81,7 @@ const Sidebar = ({
     setIsOpen(false);
 
     if (messageList.length >= 2) {
-      if (variationKey === PROMPT_EXPERIMENT_KEY) {
+      if (activeExperiment && variationKey === OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT) {
         showVariationSurvey();
       } else {
         showControlSurvey();
@@ -85,7 +93,7 @@ const Sidebar = ({
     dispatch(clearMessages());
     sendTrackEvent('edx.ui.lms.learning_assistant.clear', {
       course_id: courseId,
-      ...(variationKey ? { experiment_name: PROMPT_EXPERIMENT_FLAG, variation_key: variationKey } : {}),
+      ...experimentPayload,
     });
   };
 
diff --git a/src/components/Sidebar/index.test.jsx b/src/components/Sidebar/index.test.jsx
index fab97f9f..95933f5c 100644
--- a/src/components/Sidebar/index.test.jsx
+++ b/src/components/Sidebar/index.test.jsx
@@ -1,9 +1,10 @@
 import React from 'react';
 import { screen, act } from '@testing-library/react';
+import { useDecision } from '@optimizely/react-sdk';
 import { sendTrackEvent } from '@edx/frontend-platform/analytics';
 import { render as renderComponent } from '../../utils/utils.test';
 import { initialState } from '../../data/slice';
-import { PROMPT_EXPERIMENT_FLAG, PROMPT_EXPERIMENT_KEY } from '../../constants/experiments';
+import { OPTIMIZELY_PROMPT_EXPERIMENT_KEY, OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS } from '../../data/optimizely';
 import { showControlSurvey, showVariationSurvey } from '../../utils/surveyMonkey';
 
 import Sidebar from '.';
@@ -17,12 +18,25 @@ jest.mock('@edx/frontend-platform/analytics', () => ({
   sendTrackEvent: jest.fn(),
 }));
 
+const mockedAuthenticatedUser = { userId: 123 };
+jest.mock('@edx/frontend-platform/auth', () => ({
+  getAuthenticatedUser: () => mockedAuthenticatedUser,
+}));
+
+jest.mock('@edx/frontend-platform/analytics', () => ({
+  sendTrackEvent: jest.fn(),
+}));
+
 const mockDispatch = jest.fn();
 jest.mock('react-redux', () => ({
   ...jest.requireActual('react-redux'),
   useDispatch: () => mockDispatch,
 }));
 
+jest.mock('@optimizely/react-sdk', () => ({
+  useDecision: jest.fn(),
+}));
+
 const clearMessagesAction = 'clear-messages-action';
 jest.mock('../../data/thunks', () => ({
   clearMessages: () => 'clear-messages-action',
@@ -58,6 +72,7 @@ const render = async (props = {}, sliceState = {}) => {
 describe('<Sidebar />', () => {
   beforeEach(() => {
     jest.resetAllMocks();
+    useDecision.mockReturnValue([]);
   });
 
   describe('when it\'s open', () => {
@@ -106,15 +121,14 @@ describe('<Sidebar />', () => {
         content: 'Testing message 2',
         timestamp: +Date.now() + 1,
       }],
-      experiments: {
-        [PROMPT_EXPERIMENT_FLAG]: {
-          enabled: true,
-          variationKey: PROMPT_EXPERIMENT_KEY,
-        },
-      },
     };
 
     it('should call showVariationSurvey if experiment is active', () => {
+      useDecision.mockReturnValue([{
+        active: true,
+        variationKey: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
+      }]);
+
       render(undefined, defaultState);
 
       act(() => {
@@ -140,6 +154,11 @@ describe('<Sidebar />', () => {
     });
 
     it('should dispatch clearMessages() and call sendTrackEvent() with the expected props on clear', () => {
+      useDecision.mockReturnValue([{
+        active: true,
+        variationKey: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
+      }]);
+
       render(undefined, {
         ...defaultState,
         disclosureAcknowledged: true,
@@ -152,8 +171,8 @@ describe('<Sidebar />', () => {
       expect(mockDispatch).toHaveBeenCalledWith(clearMessagesAction);
       expect(sendTrackEvent).toHaveBeenCalledWith('edx.ui.lms.learning_assistant.clear', {
         course_id: defaultProps.courseId,
-        experiment_name: PROMPT_EXPERIMENT_FLAG,
-        variation_key: PROMPT_EXPERIMENT_KEY,
+        experiment_name: OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+        variation_key: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
       });
     });
   });
diff --git a/src/components/ToggleXpertButton/index.jsx b/src/components/ToggleXpertButton/index.jsx
index c49ad5c3..d165af1e 100644
--- a/src/components/ToggleXpertButton/index.jsx
+++ b/src/components/ToggleXpertButton/index.jsx
@@ -1,6 +1,6 @@
 import PropTypes from 'prop-types';
 import React, { useState } from 'react';
-import { useSelector } from 'react-redux';
+import { useDecision } from '@optimizely/react-sdk';
 import { sendTrackEvent } from '@edx/frontend-platform/analytics';
 import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
 import {
@@ -12,9 +12,9 @@ import {
 } from '@openedx/paragon';
 import { Close } from '@openedx/paragon/icons';
 
+import { OPTIMIZELY_PROMPT_EXPERIMENT_KEY } from '../../data/optimizely';
 import { ReactComponent as XpertLogo } from '../../assets/xpert-logo.svg';
 import './index.scss';
-import { PROMPT_EXPERIMENT_FLAG } from '../../constants/experiments';
 
 const ToggleXpert = ({
   isOpen,
@@ -22,13 +22,18 @@ const ToggleXpert = ({
   courseId,
   contentToolsEnabled,
 }) => {
-  const { experiments } = useSelector(state => state.learningAssistant);
-  const { variationKey } = experiments?.[PROMPT_EXPERIMENT_FLAG] || {};
   const [hasDismissedCTA, setHasDismissedCTA] = useState(false);
   const [isModalOpen, setIsModalOpen] = useState(true);
   const [target, setTarget] = useState(null);
   const { userId } = getAuthenticatedUser();
 
+  const [decision] = useDecision(OPTIMIZELY_PROMPT_EXPERIMENT_KEY, { autoUpdate: true }, { id: userId.toString() });
+  const { active, variationKey } = decision || {};
+  const experimentPayload = active ? {
+    experiment_name: OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+    variation_key: variationKey,
+  } : {};
+
   const handleClick = (event) => {
     // log event if the tool is opened
     if (!isOpen) {
@@ -38,7 +43,7 @@ const ToggleXpert = ({
           course_id: courseId,
           user_id: userId,
           source: event.target.id === 'toggle-button' ? 'toggle' : 'cta',
-          ...(variationKey ? { experiment_name: PROMPT_EXPERIMENT_FLAG, variation_key: variationKey } : {}),
+          ...experimentPayload,
         },
       );
     }
@@ -55,7 +60,7 @@ const ToggleXpert = ({
     localStorage.setItem('dismissedLearningAssistantCallToAction', 'true');
     sendTrackEvent('edx.ui.lms.learning_assistant.dismiss_action_message', {
       course_id: courseId,
-      ...(variationKey ? { experiment_name: PROMPT_EXPERIMENT_FLAG, variation_key: variationKey } : {}),
+      ...experimentPayload,
     });
   };
 
@@ -68,7 +73,7 @@ const ToggleXpert = ({
         course_id: courseId,
         user_id: userId,
         source: 'product-tour',
-        ...(variationKey ? { experiment_name: PROMPT_EXPERIMENT_FLAG, variation_key: variationKey } : {}),
+        ...experimentPayload,
       },
     );
   };
diff --git a/src/components/ToggleXpertButton/index.test.jsx b/src/components/ToggleXpertButton/index.test.jsx
index 96ee6e1f..d3e3d515 100644
--- a/src/components/ToggleXpertButton/index.test.jsx
+++ b/src/components/ToggleXpertButton/index.test.jsx
@@ -1,9 +1,10 @@
 import React from 'react';
 import { screen, waitFor } from '@testing-library/react';
+import { useDecision } from '@optimizely/react-sdk';
 import { sendTrackEvent } from '@edx/frontend-platform/analytics';
 import { render as renderComponent } from '../../utils/utils.test';
 import { initialState } from '../../data/slice';
-import { PROMPT_EXPERIMENT_FLAG, PROMPT_EXPERIMENT_KEY } from '../../constants/experiments';
+import { OPTIMIZELY_PROMPT_EXPERIMENT_KEY, OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS } from '../../data/optimizely';
 
 import ToggleXpert from '.';
 
@@ -16,6 +17,10 @@ jest.mock('@edx/frontend-platform/auth', () => ({
   getAuthenticatedUser: () => mockedAuthenticatedUser,
 }));
 
+jest.mock('@optimizely/react-sdk', () => ({
+  useDecision: jest.fn(),
+}));
+
 const defaultProps = {
   isOpen: false,
   setIsOpen: jest.fn(),
@@ -47,6 +52,7 @@ describe('<ToggleXpert />', () => {
   beforeEach(() => {
     window.localStorage.clear();
     jest.clearAllMocks();
+    useDecision.mockReturnValue([]);
   });
 
   describe('when it\'s closed', () => {
@@ -113,14 +119,12 @@ describe('<ToggleXpert />', () => {
   });
 
   describe('prompt experiment', () => {
-    const defaultState = {
-      experiments: {
-        [PROMPT_EXPERIMENT_FLAG]: {
-          enabled: true,
-          variationKey: PROMPT_EXPERIMENT_KEY,
-        },
-      },
-    };
+    beforeEach(() => {
+      useDecision.mockReturnValue([{
+        active: true,
+        variationKey: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
+      }]);
+    });
 
     it('should render the component', () => {
       render();
@@ -128,7 +132,7 @@ describe('<ToggleXpert />', () => {
     });
 
     it('should track the "Check it out" action', () => {
-      render(undefined, defaultState);
+      render();
 
       screen.queryByTestId('check-button').click();
 
@@ -138,14 +142,14 @@ describe('<ToggleXpert />', () => {
           course_id: defaultProps.courseId,
           user_id: mockedAuthenticatedUser.userId,
           source: 'cta',
-          experiment_name: PROMPT_EXPERIMENT_FLAG,
-          variation_key: PROMPT_EXPERIMENT_KEY,
+          experiment_name: OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+          variation_key: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
         },
       );
     });
 
     it('should track the toggle action', () => {
-      render(undefined, defaultState);
+      render();
 
       screen.queryByTestId('toggle-button').click();
 
@@ -155,14 +159,14 @@ describe('<ToggleXpert />', () => {
           course_id: defaultProps.courseId,
           user_id: mockedAuthenticatedUser.userId,
           source: 'toggle',
-          experiment_name: PROMPT_EXPERIMENT_FLAG,
-          variation_key: PROMPT_EXPERIMENT_KEY,
+          experiment_name: OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+          variation_key: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
         },
       );
     });
 
     it('should track the dismiss action', async () => {
-      render(undefined, defaultState);
+      render();
 
       // Show CTA
       await screen.queryByTestId('check-button').click();
@@ -176,8 +180,8 @@ describe('<ToggleXpert />', () => {
         'edx.ui.lms.learning_assistant.dismiss_action_message',
         {
           course_id: defaultProps.courseId,
-          experiment_name: PROMPT_EXPERIMENT_FLAG,
-          variation_key: PROMPT_EXPERIMENT_KEY,
+          experiment_name: OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+          variation_key: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
         },
       );
     });
diff --git a/src/constants/experiments.js b/src/constants/experiments.js
deleted file mode 100644
index f18efefd..00000000
--- a/src/constants/experiments.js
+++ /dev/null
@@ -1,7 +0,0 @@
-const PROMPT_EXPERIMENT_FLAG = '_cosmo__xpert_gpt_4_0_prompt';
-const PROMPT_EXPERIMENT_KEY = 'updated_prompt';
-
-export {
-  PROMPT_EXPERIMENT_FLAG,
-  PROMPT_EXPERIMENT_KEY,
-};
diff --git a/src/data/optimizely.js b/src/data/optimizely.js
index e9e6e7ba..5b1ad264 100644
--- a/src/data/optimizely.js
+++ b/src/data/optimizely.js
@@ -1,11 +1,34 @@
-import {
-  createInstance,
-} from '@optimizely/react-sdk';
+import { createInstance } from '@optimizely/react-sdk';
+import { ensureConfig, getConfig } from '@edx/frontend-platform';
 
-const OPTIMIZELY_SDK_KEY = process.env.OPTIMIZELY_FULL_STACK_SDK_KEY;
+ensureConfig([
+  'OPTIMIZELY_FULL_STACK_SDK_KEY',
+], 'Frontend Lib Learning Assistant Optimizely module');
 
-const optimizely = createInstance({
-  sdkKey: OPTIMIZELY_SDK_KEY,
-});
+/**
+ * Initializing the Optimizely instance at the top-level will not work, becaused it may be initialized before
+ * the OPTIMIZELY_FULL_STACK_SDK_KEY is available and will not be reinitialized afterward. Wrapping the initialization
+ * in a function allows components to request the instance as-needed.
+ */
+const getOptimizely = () => {
+  const OPTIMIZELY_SDK_KEY = getConfig().OPTIMIZELY_FULL_STACK_SDK_KEY;
 
-export default optimizely;
+  if (OPTIMIZELY_SDK_KEY) {
+    return createInstance({
+      sdkKey: OPTIMIZELY_SDK_KEY,
+    });
+  }
+
+  return null;
+};
+
+const OPTIMIZELY_PROMPT_EXPERIMENT_KEY = '_cosmo__xpert_gpt_4_0_prompt';
+const OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS = {
+  UPDATED_PROMPT: 'updated_prompt',
+};
+
+export {
+  getOptimizely,
+  OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+  OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS,
+};
diff --git a/src/data/optimizely.test.js b/src/data/optimizely.test.js
new file mode 100644
index 00000000..4393b1c2
--- /dev/null
+++ b/src/data/optimizely.test.js
@@ -0,0 +1,23 @@
+import { getConfig } from '@edx/frontend-platform';
+
+import { getOptimizely } from './optimizely';
+
+const originalConfig = jest.requireActual('@edx/frontend-platform').getConfig();
+jest.mock('@edx/frontend-platform', () => ({
+  ...jest.requireActual('@edx/frontend-platform'),
+  getConfig: jest.fn(),
+}));
+
+getConfig.mockImplementation(() => originalConfig);
+
+describe('Optimizely', () => {
+  test('getOptimizely returns null when OPTIMIZELY_FULL_STACK_SDK_KEY config variable is missing', () => {
+    getConfig.mockImplementation(() => ({}));
+    expect(getOptimizely()).toEqual(null);
+  });
+
+  test('getOptimizely returns null when OPTIMIZELY_FULL_STACK_SDK_KEY config variable is available', () => {
+    getConfig.mockImplementation(() => originalConfig);
+    expect(getOptimizely()).not.toEqual(null);
+  });
+});
diff --git a/src/data/slice.js b/src/data/slice.js
index fb8348e9..29dbd402 100644
--- a/src/data/slice.js
+++ b/src/data/slice.js
@@ -11,7 +11,6 @@ export const initialState = {
   disclosureAcknowledged: false,
   sidebarIsOpen: false,
   isEnabled: false,
-  experiments: {},
 };
 
 export const learningAssistantSlice = createSlice({
@@ -50,16 +49,6 @@ export const learningAssistantSlice = createSlice({
     setIsEnabled: (state, { payload }) => {
       state.isEnabled = payload;
     },
-    setExperiments: (state, { payload }) => {
-      const { decisions } = payload;
-      const experiments = {};
-      decisions
-        .filter(({ flagKey, enabled, variationKey }) => flagKey && enabled && variationKey)
-        .foreach(({ flagKey, enabled, variationKey }) => {
-          experiments[flagKey] = { enabled, variationKey };
-        });
-      state.experiments = experiments;
-    },
   },
 });
 
@@ -74,8 +63,6 @@ export const {
   setDisclosureAcknowledged,
   setSidebarIsOpen,
   setIsEnabled,
-  setExperiments,
-  clearExperiment,
 } = learningAssistantSlice.actions;
 
 export const {
diff --git a/src/data/thunks.js b/src/data/thunks.js
index 03f1635d..08cc25fa 100644
--- a/src/data/thunks.js
+++ b/src/data/thunks.js
@@ -15,12 +15,11 @@ import {
   setSidebarIsOpen,
   setIsEnabled,
 } from './slice';
-import { PROMPT_EXPERIMENT_FLAG } from '../constants/experiments';
+import { OPTIMIZELY_PROMPT_EXPERIMENT_KEY } from './optimizely';
 
-export function addChatMessage(role, content, courseId) {
+export function addChatMessage(role, content, courseId, promptExperimentVariationKey = undefined) {
   return (dispatch, getState) => {
-    const { messageList, conversationId, experiments } = getState().learningAssistant;
-    const { variationKey } = experiments?.[PROMPT_EXPERIMENT_FLAG] || {};
+    const { messageList, conversationId } = getState().learningAssistant;
 
     // Redux recommends only serializable values in the store, so we'll stringify the timestap to store in Redux.
     // When we need to operate on the Date object, we'll deserialize the string.
@@ -45,28 +44,29 @@ export function addChatMessage(role, content, courseId) {
       timestamp: message.timestamp,
       role: message.role,
       content: message.content,
-      ...(variationKey ? { experiment_name: PROMPT_EXPERIMENT_FLAG, variation_key: variationKey } : {}),
+      ...(promptExperimentVariationKey ? {
+        experiment_name: OPTIMIZELY_PROMPT_EXPERIMENT_KEY,
+        variation_key: promptExperimentVariationKey,
+      } : {}),
     });
   };
 }
 
-export function getChatResponse(courseId, unitId) {
+export function getChatResponse(courseId, unitId, promptExperimentVariationKey = undefined) {
   return async (dispatch, getState) => {
     const { userId } = getAuthenticatedUser();
     const { messageList } = getState().learningAssistant;
 
-    const { enabled, variationKey } = getState().experiments?.[PROMPT_EXPERIMENT_FLAG] || {};
-
     dispatch(setApiIsLoading(true));
     try {
-      if (enabled) {
+      if (promptExperimentVariationKey) {
         trackChatBotMessageOptimizely(userId);
       }
-      const customQueryParams = variationKey ? { responseVariation: variationKey } : {};
+      const customQueryParams = promptExperimentVariationKey ? { responseVariation: promptExperimentVariationKey } : {};
       const message = await fetchChatResponse(courseId, messageList, unitId, customQueryParams);
 
       dispatch(setApiIsLoading(false));
-      dispatch(addChatMessage(message.role, message.content, courseId));
+      dispatch(addChatMessage(message.role, message.content, courseId, promptExperimentVariationKey));
     } catch (error) {
       dispatch(setApiError());
       dispatch(setApiIsLoading(false));
diff --git a/src/hooks/useOptimizelyExperiments.js b/src/hooks/useOptimizelyExperiments.js
deleted file mode 100644
index 23601ff2..00000000
--- a/src/hooks/useOptimizelyExperiments.js
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useEffect } from 'react';
-import { useDispatch } from 'react-redux';
-import { OptimizelyDecideOption } from '@optimizely/react-sdk';
-import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
-import { setExperiments } from '../data/slice';
-import optimizely from '../data/optimizely'; // eslint-disable-line no-unused-vars
-
-const useOptimizelyExperiments = async () => {
-  const dispatch = useDispatch();
-  const { userId } = getAuthenticatedUser();
-
-  await optimizely.onReady();
-
-  useEffect(() => {
-    const user = optimizely.createUserContext(userId);
-    const decisions = user.decideAll([OptimizelyDecideOption.ENABLED_FLAGS_ONLY]);
-
-    dispatch(setExperiments({ decisions }));
-  }, [dispatch, userId]);
-};
-
-export default useOptimizelyExperiments;
diff --git a/src/hooks/useOptimizelyExperiments.test.js b/src/hooks/useOptimizelyExperiments.test.js
deleted file mode 100644
index 3f18d4c1..00000000
--- a/src/hooks/useOptimizelyExperiments.test.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import { renderHook } from '@testing-library/react-hooks';
-// import { useDispatch } from 'react-redux';
-import { useDecision } from '@optimizely/react-sdk';
-import { setExperiments } from '../data/slice';
-
-import useOptimizelyExperiment from './useOptimizelyExperiments';
-
-const optimizelyFlag = 'some-optimizely-flag';
-const userId = 123;
-const mockedDecision = { active: true, variationKey: 'test-key' };
-
-jest.mock('../data/optimizely', () => ({}));
-
-const mockDispatch = jest.fn();
-jest.mock('react-redux', () => ({
-  useDispatch: () => mockDispatch,
-}));
-
-jest.mock('@optimizely/react-sdk', () => ({
-  useDecision: jest.fn(() => ([mockedDecision])),
-}));
-
-jest.mock('../data/slice', () => ({
-  setExperiment: jest.fn(),
-}));
-
-jest.mock(
-  '@edx/frontend-platform/auth',
-  () => ({
-    getAuthenticatedUser: jest.fn(() => ({
-      userId,
-    })),
-  }),
-  { virtual: true },
-);
-
-describe('useOptimizelyExperiment()', () => {
-  describe('on normal behavior', () => {
-    beforeEach(() => {
-      renderHook(() => useOptimizelyExperiment(optimizelyFlag));
-    });
-
-    it('should call useDecision() with the expected parameters', () => {
-      expect(useDecision).toHaveBeenCalledWith(optimizelyFlag, { autoUpdate: true }, { id: userId.toString() });
-    });
-
-    it('should call setExperiment() with the expected parameters', () => {
-      renderHook(() => useOptimizelyExperiment(optimizelyFlag));
-      expect(setExperiments).toHaveBeenCalledWith({ flag: optimizelyFlag, ...mockedDecision });
-    });
-  });
-
-  describe('if useDecision returns nothing', () => {
-    beforeEach(() => {
-      useDecision.mockImplementation(() => []);
-      renderHook(() => useOptimizelyExperiment(optimizelyFlag));
-    });
-
-    it('should call setExperiment() with undefined flag and variationKey', () => {
-      renderHook(() => useOptimizelyExperiment(optimizelyFlag));
-      expect(setExperiments).toHaveBeenCalledWith({ flag: optimizelyFlag, active: undefined, variationKey: undefined });
-    });
-  });
-});
diff --git a/src/mocks/optimizely/README.rst b/src/mocks/optimizely/README.rst
new file mode 100644
index 00000000..52ef28a3
--- /dev/null
+++ b/src/mocks/optimizely/README.rst
@@ -0,0 +1,46 @@
+Optimizely Mock Implementation
+##############################
+
+Purpose
+=======
+
+This repository uses the `Optimizely SDK`_ to implement frontend experiments. Using Optimizely requires the use of an
+SDK key or datafile. This poses a problem for development locally, because it requires that an `Optimizely environment`_
+exists for local environments. When developing locally, it may be preferable not to rely explicitly on Optimizely and
+to reserve testing the Optimizely flow in a staging environment.
+
+This module contains a mock Optimizely implementation module that allows engineers to hardcode the return
+values of the functions of the `Optimizely SDK`_, namely the `useDecision hook`_.
+
+Usage
+=====
+
+The implementations of the `createInstance function`_ and the `OptimizelyProvider component`_ are no-op pass through
+functions. They do not do anything.
+
+In order to modify the experiment and how your user is bucketed, you will need to modify the `useDecision hook`_
+implementation. Use the React SDK documentation to determine how to modify this function to suit your needs.
+
+In addition, you will need to update your ``module.config.js`` file in your local checkout of the Learning MFE to
+map the ``@optimizely/react-sdk`` module to this mock module.
+
+A sample ``module.config.js`` file is shown below, but please refer to the documentation for `local module development`_
+in the `Learning MFE`_ for more information.
+
+
+.. code-block::
+
+    module.exports = {
+        localModules: [
+            { moduleName: '@edx/frontend-lib-learning-assistant', dir: '../src/frontend-lib-learning-assistant', dist: 'src' },
+            { moduleName: '@optimizely/react-sdk', dir: '../src/frontend-lib-learning-assistant/src/mocks/optimizely', dist: '.' },
+        ],
+    };
+
+.. _createInstance function: https://docs.developers.optimizely.com/feature-experimentation/docs/initialize-sdk-react
+.. _Learning MFE: https://github.com/openedx/frontend-app-learning
+.. _local module development: https://github.com/openedx/frontend-app-learning#local-module-development
+.. _Optimizely environment: https://docs.developers.optimizely.com/feature-experimentation/docs/manage-environments
+.. _OptimizelyProvider component: https://docs.developers.optimizely.com/feature-experimentation/docs/optimizelyprovider
+.. _Optimizely SDK: https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-react-sdk
+.. _useDecision hook: https://docs.developers.optimizely.com/feature-experimentation/docs/usedecision-react
diff --git a/src/mocks/optimizely/index.jsx b/src/mocks/optimizely/index.jsx
new file mode 100644
index 00000000..468b03d4
--- /dev/null
+++ b/src/mocks/optimizely/index.jsx
@@ -0,0 +1,19 @@
+/* eslint-disable no-unused-vars */
+/* eslint-disable react/prop-types */
+
+const useDecision = (experimentKey) => (
+  // To mock optimizely for local smoke testing, set the value of "enabled" to "true" and
+  // replace "replace_me" with the desired value from OPTIMIZELY_VTR_EXPERIMENT_VARIATION_KEYS
+  // which can be found in src/data/optimizely.js
+  [{ enabled: true, variationKey: 'replace_me' }]
+);
+
+const OptimizelyProvider = ({ optimizely, user, children = null }) => children;
+
+const createInstance = (args) => ({});
+
+export {
+  createInstance,
+  useDecision,
+  OptimizelyProvider,
+};
diff --git a/src/mocks/optimizely/package.json b/src/mocks/optimizely/package.json
new file mode 100644
index 00000000..c345e862
--- /dev/null
+++ b/src/mocks/optimizely/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "optimizely",
+  "version": "1.0.0",
+  "description": "Optimizely mock application for local development",
+  "main": "index.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "edX",
+  "license": "AGPL-3.0"
+}
diff --git a/src/optimizely/ExperimentsProvider.jsx b/src/optimizely/ExperimentsProvider.jsx
new file mode 100644
index 00000000..9e3dfd87
--- /dev/null
+++ b/src/optimizely/ExperimentsProvider.jsx
@@ -0,0 +1,28 @@
+import PropTypes from 'prop-types';
+import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
+import { OptimizelyProvider } from '@optimizely/react-sdk';
+
+import { getLocale } from '@edx/frontend-platform/i18n';
+import { getOptimizely } from '../data/optimizely';
+
+const ExperimentsProvider = ({ children }) => {
+  const { userId } = getAuthenticatedUser();
+  const optimizely = getOptimizely();
+
+  return (
+    <OptimizelyProvider
+      optimizely={optimizely}
+      user={{
+        id: userId.toString(),
+        attributes: { lms_language_preference: getLocale() },
+      }}
+    >{children}
+    </OptimizelyProvider>
+  );
+};
+
+ExperimentsProvider.propTypes = {
+  children: PropTypes.node.isRequired,
+};
+
+export default ExperimentsProvider;
diff --git a/src/optimizely/index.jsx b/src/optimizely/index.jsx
new file mode 100644
index 00000000..a94cb61d
--- /dev/null
+++ b/src/optimizely/index.jsx
@@ -0,0 +1,3 @@
+import ExperimentsProvider from './ExperimentsProvider';
+
+export default ExperimentsProvider;
diff --git a/src/setupTest.js b/src/setupTest.js
index 95b3726d..0d1754a5 100644
--- a/src/setupTest.js
+++ b/src/setupTest.js
@@ -1,3 +1,8 @@
 import 'core-js/stable';
 import 'regenerator-runtime/runtime';
 import '@testing-library/jest-dom';
+import { mergeConfig } from '@edx/frontend-platform';
+
+mergeConfig({
+  ...process.env,
+});
diff --git a/src/utils/optimizelyExperiment.js b/src/utils/optimizelyExperiment.js
index 1d192303..e792aef0 100644
--- a/src/utils/optimizelyExperiment.js
+++ b/src/utils/optimizelyExperiment.js
@@ -1,26 +1,16 @@
-import optimizelyInstance from '../data/optimizely';
-
-const PRODUCT_TOUR_EXP_KEY = 'la_product_tour';
-const PRODUCT_TOUR_EXP_VARIATION = 'learning_assistant_product_tour';
-
-const activateProductTourExperiment = (userId) => {
-  const variant = optimizelyInstance.activate(
-    PRODUCT_TOUR_EXP_KEY,
-    userId,
-  );
-  return variant === PRODUCT_TOUR_EXP_VARIATION;
-};
+import { getOptimizely } from '../data/optimizely';
 
 const trackChatBotLaunchOptimizely = (userId, userAttributes = {}) => {
+  const optimizelyInstance = getOptimizely();
   optimizelyInstance.track('learning_assistant_chat_click', userId, userAttributes);
 };
 
 const trackChatBotMessageOptimizely = (userId, userAttributes = {}) => {
+  const optimizelyInstance = getOptimizely();
   optimizelyInstance.track('learning_assistant_chat_message', userId, userAttributes);
 };
 
 export {
-  activateProductTourExperiment,
   trackChatBotLaunchOptimizely,
   trackChatBotMessageOptimizely,
 };
diff --git a/src/widgets/Xpert.jsx b/src/widgets/Xpert.jsx
index a53dcd0e..45fb6d23 100644
--- a/src/widgets/Xpert.jsx
+++ b/src/widgets/Xpert.jsx
@@ -4,7 +4,7 @@ import { useDispatch, useSelector } from 'react-redux';
 import { updateSidebarIsOpen, getIsEnabled } from '../data/thunks';
 import ToggleXpert from '../components/ToggleXpertButton';
 import Sidebar from '../components/Sidebar';
-import useOptimizelyExperiments from '../hooks/useOptimizelyExperiments';
+import ExperimentsProvider from '../optimizely';
 
 const Xpert = ({ courseId, contentToolsEnabled, unitId }) => {
   const dispatch = useDispatch();
@@ -14,8 +14,6 @@ const Xpert = ({ courseId, contentToolsEnabled, unitId }) => {
     sidebarIsOpen,
   } = useSelector(state => state.learningAssistant);
 
-  useOptimizelyExperiments();
-
   const setSidebarIsOpen = (isOpen) => {
     dispatch(updateSidebarIsOpen(isOpen));
   };
@@ -25,20 +23,22 @@ const Xpert = ({ courseId, contentToolsEnabled, unitId }) => {
   }, [dispatch, courseId]);
 
   return isEnabled ? (
-    <div>
-      <ToggleXpert
-        courseId={courseId}
-        isOpen={sidebarIsOpen}
-        setIsOpen={setSidebarIsOpen}
-        contentToolsEnabled={contentToolsEnabled}
-      />
-      <Sidebar
-        courseId={courseId}
-        isOpen={sidebarIsOpen}
-        setIsOpen={setSidebarIsOpen}
-        unitId={unitId}
-      />
-    </div>
+    <ExperimentsProvider>
+      <>
+        <ToggleXpert
+          courseId={courseId}
+          isOpen={sidebarIsOpen}
+          setIsOpen={setSidebarIsOpen}
+          contentToolsEnabled={contentToolsEnabled}
+        />
+        <Sidebar
+          courseId={courseId}
+          isOpen={sidebarIsOpen}
+          setIsOpen={setSidebarIsOpen}
+          unitId={unitId}
+        />
+      </>
+    </ExperimentsProvider>
   ) : null;
 };
 
diff --git a/src/widgets/Xpert.test.jsx b/src/widgets/Xpert.test.jsx
index 61690a54..1fe46460 100644
--- a/src/widgets/Xpert.test.jsx
+++ b/src/widgets/Xpert.test.jsx
@@ -8,37 +8,18 @@ import Xpert from './Xpert';
 
 import * as surveyMonkey from '../utils/surveyMonkey';
 import { render, createRandomResponseForTesting } from '../utils/utils.test';
+import { OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS } from '../data/optimizely';
 
 jest.mock('@edx/frontend-platform/analytics');
 jest.mock('@edx/frontend-platform/auth', () => ({
   getAuthenticatedUser: jest.fn(() => ({ userId: 1 })),
 }));
 
-jest.mock(
-  '@optimizely/react-sdk',
-  () => {
-    const originalModule = jest.requireActual('@optimizely/react-sdk');
-    return {
-      __esModule: true,
-      ...originalModule,
-      createInstance: jest.fn(() => ({
-        track: jest.fn(),
-      })),
-      useDecision: jest.fn(() => [{ enabled: true, variationKey: 'control' }]),
-      withOptimizely: jest.fn(
-        (Component) => (
-          function HOC(props) {
-            const newProps = {
-              ...props, optimizely: { track: jest.fn() },
-            };
-            return (<Component {...newProps} />);
-          }
-        ),
-      ),
-    };
-  },
-  { virtual: true },
-);
+jest.mock('@optimizely/react-sdk', () => ({
+  useDecision: jest.fn(),
+}));
+
+jest.mock('../optimizely', () => ({ children }) => children);
 
 // import useDecision here, after mocking, so that it can be used in tests
 import { useDecision } from '@optimizely/react-sdk'; // eslint-disable-line
@@ -72,6 +53,7 @@ beforeEach(() => {
   const responseMessage = createRandomResponseForTesting();
   jest.spyOn(api, 'default').mockResolvedValue(responseMessage);
   jest.spyOn(api, 'fetchLearningAssistantEnabled').mockResolvedValue({ enabled: true });
+  useDecision.mockReturnValue([]);
 
   window.localStorage.clear();
   // Popup modal should be ignored for all tests unless explicitly enabled. This is because
@@ -428,11 +410,15 @@ test('survey monkey survey should appear after closing sidebar', async () => {
   expect(controlSurvey).toBeCalledTimes(1);
   controlSurvey.mockRestore();
 });
+
 test('survey monkey variation survey should appear if user is in experiment', async () => {
   const variationSurvey = jest.spyOn(surveyMonkey, 'showVariationSurvey').mockReturnValueOnce(1);
   const user = userEvent.setup();
 
-  useDecision.mockImplementation(() => [{ enabled: true, variationKey: 'updated_prompt' }]);
+  useDecision.mockReturnValue([{
+    active: true,
+    variationKey: OPTIMIZELY_PROMPT_EXPERIMENT_VARIATION_KEYS.UPDATED_PROMPT,
+  }]);
 
   const surveyState = {
     learningAssistant: {