diff --git a/examples/personal_assistant_demo/README.md b/examples/personal_assistant_demo/README.md
new file mode 100644
index 00000000..47bcfbdc
--- /dev/null
+++ b/examples/personal_assistant_demo/README.md
@@ -0,0 +1,280 @@
+# Personal assistant demo
+
+In this example we'll create an agent preset that has access to:
+1. Gmail (can read your email)
+2. Google Calendar (can schedule events)
+3. SMS (can text you a message)
+
+## Initial setup
+
+For the Google APIs:
+```sh
+pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
+```
+
+For the Twilio API + listener:
+```sh
+# Outbound API requests
+pip install --upgrade twilio
+# Listener
+pip install --upgrade Flask flask-cors
+```
+
+## Setting up the Google APIs
+
+See https://developers.google.com/gmail/api/quickstart/python
+
+### Setup authentication for Google Calendar
+
+Copy the credentials file to `~/.memgpt/google_api_credentials.json`. Then, run the initial setup script that will take you to a login page:
+```sh
+python examples/personal_assistant_demo/google_calendar_test_setup.py
+```
+```
+Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=...
+Getting the upcoming 10 events
+2024-04-23T09:00:00-07:00 ...
+```
+
+### Setup authentication for Gmail
+
+Similar flow, run the authentication script to generate the token:
+```sh
+python examples/personal_assistant_demo/gmail_test_setup.py
+```
+```
+Please visit this URL to authorize this application: https://accounts.google.com/o/oauth2/auth?response_type=...
+Labels:
+CHAT
+SENT
+INBOX
+IMPORTANT
+TRASH
+...
+```
+
+## Setting up the Twilio API
+
+Create a Twilio account and set the following variables:
+```sh
+export TWILIO_ACCOUNT_SID=...
+export TWILIO_AUTH_TOKEN=...
+export TWILIO_FROM_NUMBER=...
+export TWILIO_TO_NUMBER=...
+```
+
+# Creating the agent preset
+
+## Create a custom user
+
+In the demo we'll show how MemGPT can programatically update its knowledge about you:
+```
+This is what I know so far about the user, I should expand this as I learn more about them.
+
+Name: Charles Packer
+Gender: Male
+Occupation: CS PhD student working on an AI project with collaborator Sarah Wooders
+
+Notes about their preferred communication style + working habits:
+- wakes up at around 7am
+- enjoys using (and receiving!) emojis in messages, especially funny combinations of emojis
+- prefers sending and receiving shorter messages
+- does not like "robotic" sounding assistants, e.g. assistants that say "How can I assist you today?"
+```
+
+```sh
+memgpt add human -f examples/personal_assistant_demo/charles.txt --name charles
+```
+
+## Linking the functions
+
+The preset (shown below) and functions are provided for you, so you just need to copy/link them.
+
+```sh
+cp examples/personal_assistant_demo/google_calendar.py ~/.memgpt/functions/
+cp examples/personal_assistant_demo/twilio_messaging.py ~/.memgpt/functions/
+```
+
+(or use the dev portal)
+
+## Creating the preset
+
+```yaml
+system_prompt: "memgpt_chat"
+functions:
+ - "send_message"
+ - "pause_heartbeats"
+ - "core_memory_append"
+ - "core_memory_replace"
+ - "conversation_search"
+ - "conversation_search_date"
+ - "archival_memory_insert"
+ - "archival_memory_search"
+ - "schedule_event"
+ - "send_text_message"
+```
+
+```sh
+memgpt add preset -f examples/personal_assistant_demo/personal_assistant_preset.yaml --name pa_preset
+```
+
+## Creating an agent with the preset
+
+Now we should be able to create an agent with the preset. Make sure to record the `agent_id`:
+
+```sh
+memgpt run --preset pa_preset --persona sam_pov --human charles --stream
+```
+```
+? Would you like to select an existing agent? No
+
+🧬 Creating new agent...
+-> 🤖 Using persona profile: 'sam_pov'
+-> 🧑 Using human profile: 'basic'
+🎉 Created new agent 'DelicateGiraffe' (id=4c4e97c9-ad8e-4065-b716-838e5d6f7f7b)
+
+Hit enter to begin (will request first MemGPT message)
+
+
+💭 Unprecedented event, Charles logged into the system for the first time. Warm welcome would set a positive
+tone for our future interactions. Don't forget the emoji, he appreciates those little gestures.
+🤖 Hello Charles! 👋 Great to have you here. I've been looking forward to our conversations! 😄
+```
+
+```sh
+AGENT_ID="4c4e97c9-ad8e-4065-b716-838e5d6f7f7b"
+```
+
+# Running the agent with Gmail + SMS listeners
+
+The MemGPT agent can send outbound SMS messages and schedule events with the new tools `send_text_message` and `schedule_event`, but we also want messages to be sent to the agent when:
+1. A new email arrives in our inbox
+2. An SMS is sent to the phone number used by the agent
+
+## Running the Gmail listener
+
+Start the Gmail listener (this will send "new email" updates to the MemGPT server when a new email arrives):
+```sh
+python examples/personal_assistant_demo/twilio_flask_listener.py $AGENT_ID
+```
+
+## Running the Twilio listener
+
+Start the Python Flask server (this will send "new SMS" updates to the MemGPT server when a new SMS arrives):
+```sh
+python examples/personal_assistant_demo/twilio_flask_listener.py $AGENT_ID
+```
+
+Run `ngrok` to expose your local Flask server to a public IP (Twilio will POST to this server when an inbound SMS hits):
+```sh
+# the flask listener script is hardcoded to listen on port 8284
+ngrok http 8284
+```
+
+## Run the MemGPT server
+
+Run the MemGPT server to turn on the agent service:
+```sh
+memgpt server --debug
+```
+
+# Example interaction
+
+In the CLI:
+```
+% memgpt run --preset pa_preset --persona pa_persona_strict --human charles --stream --agent personalassistant
+
+🧬 Creating new agent...
+-> 🤖 Using persona profile: 'sam_pov'
+-> 🧑 Using human profile: 'basic'
+🎉 Created new agent 'personalassistant' (id=8271f819-d470-435b-9689-476380aefd27)
+
+Hit enter to begin (will request first MemGPT message)
+
+
+
+💭 Welcoming the first-time user. Maintain persona's communication style and stay engaging. Remember to use emojis for
+a more familiar conversation style.
+🤖 Hello Charles, welcome! 🎉 Let's embark on this journey together. Please, think of me as your companion in
+exploration and understanding. How can we make today a productive endeavor? 🚀
+
+> Enter your message: when you schedule 1-1s, please title them "x <> y", eg "charles <> sarah". dont forget thx
+
+💭 The user wants a specific format for scheduling 1-1 meetings. Remember this preference and add it to his profile in
+the core memory for future use. Also, acknowledge his request promptly.
+core_memory_append({
+ "name": "human",
+ "content": "When scheduling 1-1 meetings on his calendar, he prefers the title to be in the format of 'x <> y'.",
+ "request_heartbeat": true
+})
+
+⚡🧠 [function] updating memory with core_memory_append
+
+💭 Now that the user's preference is stored, send a confirmation message ensuring him that his request has been
+understood and noted. Let's keep the tone light and potentially use one of his preferred emojis.
+🤖 Absolutely, Charles. 📝 I've noted your preference for scheduling 1-1s as 'x <> y'. You can count on me! 🚀 Any
+other preferences you'd like me to keep in mind?
+
+
+> Enter your message: please don't schedule meetings without confirming with me first
+
+💭 Another important note from Charles. I should add this to his profile in my core memory. It's crucial to respect the
+user's preferences and ask for prior approval before scheduling any meetings. Let's also acknowledge his request and
+reassure him about our collaborative approach.
+core_memory_append({
+ "name": "human",
+ "content": "Prefers to confirm before scheduling any meetings.",
+ "request_heartbeat": true
+})
+
+⚡🧠 [function] updating memory with core_memory_append
+
+💭 Now, let's acknowledge Charles' preference and continue the conversation. Keep the tone friendly and proactive in
+helping him, and remember to use emojis that Charles likes, embedding them in the message subtly.
+🤖 Consider it done, Charles! 👍🏼 I'll make sure to confirm with you before scheduling any meetings. How about we
+conquer some more tasks today? 🚀
+
+> Enter your message: i'm gonna be afk for the rest of the day, if you need to reach me you gotta text me, ok? cya text
+ only thx
+
+💭 Charles is going "away from keyboard" and wants future communications via text. I should respect his preference and
+reassure him about this. Add this preference to his profile so I can recall it in future interactions while he's away.
+core_memory_append({
+ "name": "human",
+ "content": "When AFK, prefers to communicate over text message only.",
+ "request_heartbeat": true
+})
+
+⚡🧠 [function] updating memory with core_memory_append
+
+💭 Charles has expressed a preference for text messages while he's away. Let's acknowledge this request in a final text
+message to him today and wish him well. Keep in mind to use the send_text_message function instead of the regular
+send_message function now.
+send_text_message({
+ "message": "Got it, Charles! 👍 I'll reach out to you via text if there's anything. Have a great day and see you
+soon! 🙌",
+ "request_heartbeat": false
+})
+
+> Enter your message:
+```
+
+Then inside WhatsApp (or SMS if you used Twilio SMS):
+
+
+
+Then I sent a dummy email:
+```
+[URGENT] need to meet
+
+let's meet april 25th thurs
+
+whatever time works best for you
+
+- dave
+```
+
+Follow-up inside WhatsApp:
+
+
+
diff --git a/examples/personal_assistant_demo/charles.txt b/examples/personal_assistant_demo/charles.txt
new file mode 100644
index 00000000..1932e933
--- /dev/null
+++ b/examples/personal_assistant_demo/charles.txt
@@ -0,0 +1,11 @@
+This is what I know so far about the user, I should expand this as I learn more about them.
+
+Name: Charles Packer
+Gender: Male
+Occupation: CS PhD student working on an AI project with collaborator Sarah Wooders
+
+Notes about their preferred communication style + working habits:
+- wakes up at around 7am
+- enjoys using (and receiving!) emojis in messages, especially funny combinations of emojis
+- prefers sending and receiving shorter messages
+- does not like "robotic" sounding assistants, e.g. assistants that say "How can I assist you today?"
diff --git a/examples/personal_assistant_demo/gmail_test_setup.py b/examples/personal_assistant_demo/gmail_test_setup.py
new file mode 100644
index 00000000..dae38298
--- /dev/null
+++ b/examples/personal_assistant_demo/gmail_test_setup.py
@@ -0,0 +1,56 @@
+import os.path
+
+from google.auth.transport.requests import Request
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+# If modifying these scopes, delete the file token.json.
+SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
+
+TOKEN_PATH = os.path.expanduser("~/.memgpt/gmail_token.json")
+CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
+
+
+def main():
+ """Shows basic usage of the Gmail API.
+ Lists the user's Gmail labels.
+ """
+ creds = None
+ # The file token.json stores the user's access and refresh tokens, and is
+ # created automatically when the authorization flow completes for the first
+ # time.
+ if os.path.exists(TOKEN_PATH):
+ creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
+ # If there are no (valid) credentials available, let the user log in.
+ if not creds or not creds.valid:
+ if creds and creds.expired and creds.refresh_token:
+ creds.refresh(Request())
+ else:
+ flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
+ creds = flow.run_local_server(port=0)
+ # Save the credentials for the next run
+ with open(TOKEN_PATH, "w") as token:
+ token.write(creds.to_json())
+
+ try:
+ # Call the Gmail API
+ service = build("gmail", "v1", credentials=creds)
+ results = service.users().labels().list(userId="me").execute()
+ labels = results.get("labels", [])
+
+ if not labels:
+ print("No labels found.")
+ return
+ print("Labels:")
+ for label in labels:
+ print(label["name"])
+
+ except HttpError as error:
+ # TODO(developer) - Handle errors from gmail API.
+ print(f"An error occurred: {error}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/personal_assistant_demo/gmail_unread_polling_listener.py b/examples/personal_assistant_demo/gmail_unread_polling_listener.py
new file mode 100644
index 00000000..74d77965
--- /dev/null
+++ b/examples/personal_assistant_demo/gmail_unread_polling_listener.py
@@ -0,0 +1,143 @@
+import base64
+import os.path
+import requests
+import sys
+import time
+from email import message_from_bytes
+
+from google.auth.transport.requests import Request
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+# If modifying these scopes, delete the file token.json.
+SCOPES = ["https://www.googleapis.com/auth/gmail.readonly"]
+TOKEN_PATH = os.path.expanduser("~/.memgpt/gmail_token.json")
+CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
+
+DELAY = 1
+
+MEMGPT_SERVER_URL = "http://127.0.0.1:8283"
+MEMGPT_TOKEN = os.getenv("MEMGPT_SERVER_PASS")
+assert MEMGPT_TOKEN, f"Missing env variable MEMGPT_SERVER_PASS"
+MEMGPT_AGENT_ID = sys.argv[1] if len(sys.argv) > 1 else None
+assert MEMGPT_AGENT_ID, f"Missing agent ID (pass as arg)"
+
+
+def route_reply_to_memgpt_api(message):
+ # send a POST request to a MemGPT server
+
+ url = f"{MEMGPT_SERVER_URL}/api/agents/{MEMGPT_AGENT_ID}/messages"
+ headers = {
+ "accept": "application/json",
+ "authorization": f"Bearer {MEMGPT_TOKEN}",
+ "content-type": "application/json",
+ }
+ data = {
+ "stream": False,
+ "role": "system",
+ "message": f"[EMAIL NOTIFICATION] {message}",
+ }
+
+ try:
+ response = requests.post(url, headers=headers, json=data)
+ print("Got response:", response.text)
+ except Exception as e:
+ print("Sending message failed:", str(e))
+
+
+def decode_base64url(data):
+ """Decode base64, padding being optional."""
+ data += "=" * ((4 - len(data) % 4) % 4)
+ return base64.urlsafe_b64decode(data)
+
+
+def parse_email(message):
+ """Parse email content using the email library."""
+ msg_bytes = decode_base64url(message["raw"])
+ email_message = message_from_bytes(msg_bytes)
+ return email_message
+
+
+def process_email(message) -> dict:
+ # print(f"New email from {email_message['from']}: {email_message['subject']}")
+ email_message = parse_email(message)
+ body_plain_all = ""
+ body_html_all = ""
+ if email_message.is_multipart():
+ for part in email_message.walk():
+ if part.get_content_type() == "text/plain":
+ body_plain = str(part.get_payload(decode=True).decode("utf-8"))
+ # print(body_plain)
+ body_plain_all += body_plain
+ elif part.get_content_type() == "text/html":
+ body_html = str(part.get_payload(decode=True).decode("utf-8"))
+ # print(body_html)
+ body_html_all += body_html
+ else:
+ body_plain_all = print(email_message.get_payload(decode=True).decode("utf-8"))
+
+ return {
+ "from": email_message["from"],
+ "subject": email_message["subject"],
+ "body": body_plain_all,
+ }
+
+
+def main():
+ """Monitors for new emails and prints their titles."""
+ creds = None
+ if os.path.exists(TOKEN_PATH):
+ creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
+
+ if not creds or not creds.valid:
+ if creds and creds.expired and creds.refresh_token:
+ creds.refresh(Request())
+ else:
+ flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
+ creds = flow.run_local_server(port=0)
+ with open(TOKEN_PATH, "w") as token:
+ token.write(creds.to_json())
+
+ service = build("gmail", "v1", credentials=creds)
+ last_checked = None
+ seen_ids = set() # Set to track seen email IDs
+
+ try:
+ # Initially populate the seen_ids with all current unread emails
+ print("Grabbing initial state...")
+ initial_results = service.users().messages().list(userId="me", q="is:unread", maxResults=500).execute()
+ initial_messages = initial_results.get("messages", [])
+ seen_ids.update(msg["id"] for msg in initial_messages)
+
+ print("Listening...")
+ while True:
+ results = service.users().messages().list(userId="me", q="is:unread", maxResults=5).execute()
+ messages = results.get("messages", [])
+ if messages:
+ for message in messages:
+ if message["id"] not in seen_ids:
+ seen_ids.add(message["id"])
+ msg = service.users().messages().get(userId="me", id=message["id"], format="raw").execute()
+
+ # Optionally mark the message as read here if required
+ email_obj = process_email(msg)
+ msg_str = f"New email from {email_obj['from']}: {email_obj['subject']}, body: {email_obj['body'][:100]}"
+
+ # Hard check to ignore emails unless
+ # if not (
+ # "email@address" in email_obj["from"]
+ # ):
+ # print("ignoring")
+ # else:
+ print(msg_str)
+ route_reply_to_memgpt_api(msg_str)
+
+ time.sleep(DELAY) # Wait for N seconds before checking again
+ except HttpError as error:
+ print(f"An error occurred: {error}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/personal_assistant_demo/google_calendar.py b/examples/personal_assistant_demo/google_calendar.py
new file mode 100644
index 00000000..4cade72e
--- /dev/null
+++ b/examples/personal_assistant_demo/google_calendar.py
@@ -0,0 +1,98 @@
+# Enabling API control on Google Calendar requires a few steps:
+# https://developers.google.com/calendar/api/quickstart/python
+# including:
+# pip install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib
+
+import os
+import traceback
+import datetime
+import os.path
+from typing import Optional, List
+
+from google.auth.transport.requests import Request
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+# If modifying these scopes, delete the file token.json.
+# SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
+SCOPES = ["https://www.googleapis.com/auth/calendar"]
+TOKEN_PATH = os.path.expanduser("~/.memgpt/gcal_token.json")
+CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
+
+
+def schedule_event(
+ self,
+ title: str,
+ start: str,
+ end: str,
+ # attendees: Optional[List[str]] = None,
+ # attendees: Optional[list[str]] = None,
+ description: Optional[str] = None,
+ # timezone: Optional[str] = "America/Los_Angeles",
+) -> str:
+ """
+ Schedule an event on the user's Google Calendar. Start and end time must be in ISO 8601 format, e.g. February 1st 2024 at noon PT would be "2024-02-01T12:00:00-07:00".
+
+ Args:
+ title (str): Event name
+ start (str): Start time in ISO 8601 format (date, time, and timezone offset)
+ end (str): End time in ISO 8601 format (date, time, and timezone offset)
+ description (Optional[str]): Expanded description of the event
+
+ Returns:
+ str: The status of the event scheduling request.
+ """
+
+ creds = None
+ # The file token.json stores the user's access and refresh tokens, and is
+ # created automatically when the authorization flow completes for the first
+ # time.
+ if os.path.exists(TOKEN_PATH):
+ creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
+ # If there are no (valid) credentials available, let the user log in.
+ if not creds or not creds.valid:
+ if creds and creds.expired and creds.refresh_token:
+ creds.refresh(Request())
+ else:
+ flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
+ creds = flow.run_local_server(port=0)
+ # Save the credentials for the next run
+ with open(TOKEN_PATH, "w") as token:
+ token.write(creds.to_json())
+
+ #### Create an event
+ # Refer to the Python quickstart on how to setup the environment:
+ # https://developers.google.com/calendar/quickstart/python
+ # Change the scope to 'https://www.googleapis.com/auth/calendar' and delete any
+ # stored credentials.
+ try:
+ service = build("calendar", "v3", credentials=creds)
+
+ event = {
+ "summary": title,
+ # "location": "800 Howard St., San Francisco, CA 94103",
+ "start": {
+ "dateTime": start,
+ "timeZone": "America/Los_Angeles",
+ },
+ "end": {
+ "dateTime": end,
+ "timeZone": "America/Los_Angeles",
+ },
+ }
+
+ # if attendees is not None:
+ # event["attendees"] = attendees
+
+ if description is not None:
+ event["description"] = description
+
+ event = service.events().insert(calendarId="primary", body=event).execute()
+ return "Event created: %s" % (event.get("htmlLink"))
+
+ except HttpError as error:
+ traceback.print_exc()
+
+ return f"An error occurred while trying to create an event: {str(error)}"
diff --git a/examples/personal_assistant_demo/google_calendar_preset.yaml b/examples/personal_assistant_demo/google_calendar_preset.yaml
new file mode 100644
index 00000000..158e2643
--- /dev/null
+++ b/examples/personal_assistant_demo/google_calendar_preset.yaml
@@ -0,0 +1,11 @@
+system_prompt: "memgpt_chat"
+functions:
+ - "send_message"
+ - "pause_heartbeats"
+ - "core_memory_append"
+ - "core_memory_replace"
+ - "conversation_search"
+ - "conversation_search_date"
+ - "archival_memory_insert"
+ - "archival_memory_search"
+ - "schedule_event"
diff --git a/examples/personal_assistant_demo/google_calendar_test_setup.py b/examples/personal_assistant_demo/google_calendar_test_setup.py
new file mode 100644
index 00000000..be86f918
--- /dev/null
+++ b/examples/personal_assistant_demo/google_calendar_test_setup.py
@@ -0,0 +1,111 @@
+import datetime
+import os.path
+
+from google.auth.transport.requests import Request
+from google.oauth2.credentials import Credentials
+from google_auth_oauthlib.flow import InstalledAppFlow
+from googleapiclient.discovery import build
+from googleapiclient.errors import HttpError
+
+# If modifying these scopes, delete the file token.json.
+# SCOPES = ["https://www.googleapis.com/auth/calendar.readonly"]
+SCOPES = ["https://www.googleapis.com/auth/calendar"]
+
+TOKEN_PATH = os.path.expanduser("~/.memgpt/gcal_token.json")
+CREDENTIALS_PATH = os.path.expanduser("~/.memgpt/google_api_credentials.json")
+
+
+def main():
+ """Shows basic usage of the Google Calendar API.
+ Prints the start and name of the next 10 events on the user's calendar.
+ """
+ creds = None
+ # The file token.json stores the user's access and refresh tokens, and is
+ # created automatically when the authorization flow completes for the first
+ # time.
+ if os.path.exists(TOKEN_PATH):
+ creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES)
+ # If there are no (valid) credentials available, let the user log in.
+ if not creds or not creds.valid:
+ if creds and creds.expired and creds.refresh_token:
+ creds.refresh(Request())
+ else:
+ flow = InstalledAppFlow.from_client_secrets_file(CREDENTIALS_PATH, SCOPES)
+ creds = flow.run_local_server(port=0)
+ # Save the credentials for the next run
+ with open(TOKEN_PATH, "w") as token:
+ token.write(creds.to_json())
+
+ try:
+ service = build("calendar", "v3", credentials=creds)
+
+ # Call the Calendar API
+ now = datetime.datetime.utcnow().isoformat() + "Z" # 'Z' indicates UTC time
+ print("Getting the upcoming 10 events")
+ events_result = (
+ service.events()
+ .list(
+ calendarId="primary",
+ timeMin=now,
+ maxResults=10,
+ singleEvents=True,
+ orderBy="startTime",
+ )
+ .execute()
+ )
+ events = events_result.get("items", [])
+
+ if not events:
+ print("No upcoming events found.")
+ return
+
+ # Prints the start and name of the next 10 events
+ for event in events:
+ start = event["start"].get("dateTime", event["start"].get("date"))
+ print(start, event["summary"])
+
+ except HttpError as error:
+ print(f"An error occurred: {error}")
+
+ #### Create an event
+ # Refer to the Python quickstart on how to setup the environment:
+ # https://developers.google.com/calendar/quickstart/python
+ # Change the scope to 'https://www.googleapis.com/auth/calendar' and delete any
+ # stored credentials.
+ # try:
+ # service = build("calendar", "v3", credentials=creds)
+
+ # event = {
+ # "summary": "GCAL API TEST EVENT",
+ # # "location": "800 Howard St., San Francisco, CA 94103",
+ # "description": "A chance to hear more about Google's developer products.",
+ # "start": {
+ # "dateTime": "2024-04-23T09:00:00-07:00",
+ # "timeZone": "America/Los_Angeles",
+ # },
+ # "end": {
+ # "dateTime": "2024-04-24T17:00:00-07:00",
+ # "timeZone": "America/Los_Angeles",
+ # },
+ # # "recurrence": ["RRULE:FREQ=DAILY;COUNT=2"],
+ # "attendees": [
+ # {"email": "packercharles@gmail.com"},
+ # ],
+ # # "reminders": {
+ # # "useDefault": False,
+ # # "overrides": [
+ # # {"method": "email", "minutes": 24 * 60},
+ # # {"method": "popup", "minutes": 10},
+ # # ],
+ # # },
+ # }
+
+ # event = service.events().insert(calendarId="primary", body=event).execute()
+ # print("Event created: %s" % (event.get("htmlLink")))
+
+ except HttpError as error:
+ print(f"An error occurred: {error}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/personal_assistant_demo/personal_assistant.txt b/examples/personal_assistant_demo/personal_assistant.txt
new file mode 100644
index 00000000..e69de29b
diff --git a/examples/personal_assistant_demo/personal_assistant_preset.yaml b/examples/personal_assistant_demo/personal_assistant_preset.yaml
new file mode 100644
index 00000000..293c72e0
--- /dev/null
+++ b/examples/personal_assistant_demo/personal_assistant_preset.yaml
@@ -0,0 +1,12 @@
+system_prompt: "memgpt_chat"
+functions:
+ - "send_message"
+ - "pause_heartbeats"
+ - "core_memory_append"
+ - "core_memory_replace"
+ - "conversation_search"
+ - "conversation_search_date"
+ - "archival_memory_insert"
+ - "archival_memory_search"
+ - "schedule_event"
+ - "send_text_message"
\ No newline at end of file
diff --git a/examples/personal_assistant_demo/twilio_flask_listener.py b/examples/personal_assistant_demo/twilio_flask_listener.py
new file mode 100644
index 00000000..881179d4
--- /dev/null
+++ b/examples/personal_assistant_demo/twilio_flask_listener.py
@@ -0,0 +1,76 @@
+import os
+import requests
+import sys
+
+from flask import Flask, request, Response
+from flask_cors import CORS
+
+app = Flask(__name__)
+CORS(app)
+
+from twilio.twiml.messaging_response import MessagingResponse
+
+app = Flask(__name__)
+CORS(app)
+
+MEMGPT_SERVER_URL = "http://127.0.0.1:8283"
+MEMGPT_TOKEN = os.getenv("MEMGPT_SERVER_PASS")
+assert MEMGPT_TOKEN, f"Missing env variable MEMGPT_SERVER_PASS"
+MEMGPT_AGENT_ID = sys.argv[1] if len(sys.argv) > 1 else None
+assert MEMGPT_AGENT_ID, f"Missing agent ID (pass as arg)"
+
+
+@app.route("/test", methods=["POST"])
+def test():
+ print(request.headers)
+ return "Headers received. Check your console."
+
+
+def route_reply_to_memgpt_api(message):
+ # send a POST request to a MemGPT server
+
+ url = f"{MEMGPT_SERVER_URL}/api/agents/{MEMGPT_AGENT_ID}/messages"
+ headers = {
+ "accept": "application/json",
+ "authorization": f"Bearer {MEMGPT_TOKEN}",
+ "content-type": "application/json",
+ }
+ data = {
+ "stream": False,
+ "role": "system",
+ "message": f"[SMS MESSAGE NOTIFICATION - you MUST use send_text_message NOT send_message if you want to reply to the text thread] {message}",
+ }
+
+ try:
+ response = requests.post(url, headers=headers, json=data)
+ print("Got response:", response.text)
+ except Exception as e:
+ print("Sending message failed:", str(e))
+
+
+@app.route("/sms", methods=["POST"])
+def sms_reply():
+ """Respond to incoming calls with a simple text message."""
+ # Fetch the message
+ message_body = request.form["Body"]
+ from_number = request.form["From"]
+
+ # print(f"New message from {from_number}: {message_body}")
+ msg_str = f"New message from {from_number}: {message_body}"
+ print(msg_str)
+
+ route_reply_to_memgpt_api(msg_str)
+ return str("status = OK")
+
+ # Start our response
+ # resp = MessagingResponse()
+
+ # Add a message
+ # resp.message("Hello, thanks for messaging!")
+
+ # return str(resp)
+
+
+if __name__ == "__main__":
+ # app.run(debug=True)
+ app.run(host="0.0.0.0", port=8284, debug=True)
diff --git a/examples/personal_assistant_demo/twilio_messaging.py b/examples/personal_assistant_demo/twilio_messaging.py
new file mode 100644
index 00000000..fa642f7a
--- /dev/null
+++ b/examples/personal_assistant_demo/twilio_messaging.py
@@ -0,0 +1,41 @@
+# Download the helper library from https://www.twilio.com/docs/python/install
+import os
+import traceback
+
+from twilio.rest import Client
+
+
+def send_text_message(self, message: str) -> str:
+ """
+ Sends an SMS message to the user's phone / cellular device.
+
+ Args:
+ message (str): The contents of the message to send.
+
+ Returns:
+ str: The status of the text message.
+ """
+ # Find your Account SID and Auth Token at twilio.com/console
+ # and set the environment variables. See http://twil.io/secure
+ account_sid = os.environ["TWILIO_ACCOUNT_SID"]
+ auth_token = os.environ["TWILIO_AUTH_TOKEN"]
+ client = Client(account_sid, auth_token)
+
+ from_number = os.getenv("TWILIO_FROM_NUMBER")
+ to_number = os.getenv("TWILIO_TO_NUMBER")
+ assert from_number and to_number
+ # assert from_number.startswith("+1") and len(from_number) == 12, from_number
+ # assert to_number.startswith("+1") and len(to_number) == 12, to_number
+
+ try:
+ message = client.messages.create(
+ body=str(message),
+ from_=from_number,
+ to=to_number,
+ )
+ return "Message was successfully sent."
+
+ except Exception as e:
+ traceback.print_exc()
+
+ return f"Message failed to send with error: {str(e)}"
diff --git a/examples/personal_assistant_demo/twilio_messaging_preset.yaml b/examples/personal_assistant_demo/twilio_messaging_preset.yaml
new file mode 100644
index 00000000..344d2f2e
--- /dev/null
+++ b/examples/personal_assistant_demo/twilio_messaging_preset.yaml
@@ -0,0 +1,11 @@
+system_prompt: "memgpt_chat"
+functions:
+ - "send_message"
+ - "pause_heartbeats"
+ - "core_memory_append"
+ - "core_memory_replace"
+ - "conversation_search"
+ - "conversation_search_date"
+ - "archival_memory_insert"
+ - "archival_memory_search"
+ - "send_text_message"
diff --git a/memgpt/functions/functions.py b/memgpt/functions/functions.py
index 329f6095..78656d18 100644
--- a/memgpt/functions/functions.py
+++ b/memgpt/functions/functions.py
@@ -149,7 +149,9 @@ def load_all_function_sets(merge: bool = True, ignore_duplicates: bool = True) -
v["tags"] = tags
schemas_and_functions[module_name] = function_set
except ValueError as e:
- printd(f"Error loading function set '{module_name}': {e}")
+ err = f"Error loading function set '{module_name}': {e}"
+ printd(err)
+ warnings.warn(err)
if merge:
# Put all functions from all sets into the same level dict