Repository: airtai/fastagency

Issue:
Mesop: Improve multiple choice input
Mesop: Past Chats

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/airtai/fastagency/pull/198?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) Report
Attention: Patch coverage is `0%` with `128 lines` in your changes missing coverage. Please review.

| [Files with missing lines](https://app.codecov.io/gh/airtai/fastagency/pull/198?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) | Patch % | Lines |
|---|---|---|
| [fastagency/ui/mesop/message.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fmessage.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9tZXNzYWdlLnB5) | 0.00% | [68 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |
| [.../user\_guide/custom\_user\_interactions/main\_mesop.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=docs%2Fdocs_src%2Fuser_guide%2Fcustom_user_interactions%2Fmain_mesop.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZG9jcy9kb2NzX3NyYy91c2VyX2d1aWRlL2N1c3RvbV91c2VyX2ludGVyYWN0aW9ucy9tYWluX21lc29wLnB5) | 0.00% | [32 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |
| [...cs\_src/user\_guide/external\_rest\_apis/main\_mesop.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=docs%2Fdocs_src%2Fuser_guide%2Fexternal_rest_apis%2Fmain_mesop.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZG9jcy9kb2NzX3NyYy91c2VyX2d1aWRlL2V4dGVybmFsX3Jlc3RfYXBpcy9tYWluX21lc29wLnB5) | 0.00% | [13 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |
| [fastagency/ui/mesop/data\_model.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fdata_model.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9kYXRhX21vZGVsLnB5) | 0.00% | [7 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |
| [fastagency/ui/mesop/components/inputs.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fcomponents%2Finputs.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9jb21wb25lbnRzL2lucHV0cy5weQ==) | 0.00% | [4 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |
| [...cs\_src/user\_guide/custom\_user\_interactions/main.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=docs%2Fdocs_src%2Fuser_guide%2Fcustom_user_interactions%2Fmain.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZG9jcy9kb2NzX3NyYy91c2VyX2d1aWRlL2N1c3RvbV91c2VyX2ludGVyYWN0aW9ucy9tYWluLnB5) | 0.00% | [2 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |
| [fastagency/ui/mesop/main.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fmain.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9tYWluLnB5) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |
| [fastagency/ui/mesop/send\_prompt.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fsend_prompt.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9zZW5kX3Byb21wdC5weQ==) | 0.00% | [1 Missing :warning: ](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) |

| [Files with missing lines](https://app.codecov.io/gh/airtai/fastagency/pull/198?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai) | Coverage Δ | |
|---|---|---|
| [fastagency/studio/models/agents/assistant.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fstudio%2Fmodels%2Fagents%2Fassistant.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS9zdHVkaW8vbW9kZWxzL2FnZW50cy9hc3Npc3RhbnQucHk=) | `83.33% <ø> (ø)` | |
| [fastagency/ui/mesop/base.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fbase.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9iYXNlLnB5) | `0.00% <ø> (ø)` | |
| [fastagency/ui/mesop/main.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fmain.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9tYWluLnB5) | `0.00% <0.00%> (ø)` | |
| [fastagency/ui/mesop/send\_prompt.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fsend_prompt.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9zZW5kX3Byb21wdC5weQ==) | `0.00% <0.00%> (ø)` | |
| [...cs\_src/user\_guide/custom\_user\_interactions/main.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=docs%2Fdocs_src%2Fuser_guide%2Fcustom_user_interactions%2Fmain.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZG9jcy9kb2NzX3NyYy91c2VyX2d1aWRlL2N1c3RvbV91c2VyX2ludGVyYWN0aW9ucy9tYWluLnB5) | `0.00% <0.00%> (ø)` | |
| [fastagency/ui/mesop/components/inputs.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fcomponents%2Finputs.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9jb21wb25lbnRzL2lucHV0cy5weQ==) | `0.00% <0.00%> (ø)` | |
| [fastagency/ui/mesop/data\_model.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fdata_model.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9kYXRhX21vZGVsLnB5) | `0.00% <0.00%> (ø)` | |
| [...cs\_src/user\_guide/external\_rest\_apis/main\_mesop.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=docs%2Fdocs_src%2Fuser_guide%2Fexternal_rest_apis%2Fmain_mesop.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZG9jcy9kb2NzX3NyYy91c2VyX2d1aWRlL2V4dGVybmFsX3Jlc3RfYXBpcy9tYWluX21lc29wLnB5) | `0.00% <0.00%> (ø)` | |
| [.../user\_guide/custom\_user\_interactions/main\_mesop.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=docs%2Fdocs_src%2Fuser_guide%2Fcustom_user_interactions%2Fmain_mesop.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZG9jcy9kb2NzX3NyYy91c2VyX2d1aWRlL2N1c3RvbV91c2VyX2ludGVyYWN0aW9ucy9tYWluX21lc29wLnB5) | `0.00% <0.00%> (ø)` | |
| [fastagency/ui/mesop/message.py](https://app.codecov.io/gh/airtai/fastagency/pull/198?src=pr&el=tree&filepath=fastagency%2Fui%2Fmesop%2Fmessage.py&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=airtai#diff-ZmFzdGFnZW5jeS91aS9tZXNvcC9tZXNzYWdlLnB5) | `0.00% <0.00%> (ø)` | |


Patch:
diff --git a/docs/docs/SUMMARY.md b/docs/docs/SUMMARY.md
index c1424825..6aa8b9dc 100644
--- a/docs/docs/SUMMARY.md
+++ b/docs/docs/SUMMARY.md
@@ -256,17 +256,19 @@ search:
                         - [header](api/fastagency/ui/mesop/components/ui_common/header.md)
                 - data_model
                     - [Conversation](api/fastagency/ui/mesop/data_model/Conversation.md)
+                    - [ConversationMessage](api/fastagency/ui/mesop/data_model/ConversationMessage.md)
                     - [State](api/fastagency/ui/mesop/data_model/State.md)
                 - main
                     - [conversation_box](api/fastagency/ui/mesop/main/conversation_box.md)
                     - [conversation_starter_box](api/fastagency/ui/mesop/main/conversation_starter_box.md)
                     - [get_ui](api/fastagency/ui/mesop/main/get_ui.md)
                     - [home_page](api/fastagency/ui/mesop/main/home_page.md)
-                    - [on_user_feedback](api/fastagency/ui/mesop/main/on_user_feedback.md)
                     - [past_conversations_box](api/fastagency/ui/mesop/main/past_conversations_box.md)
                     - [send_prompt](api/fastagency/ui/mesop/main/send_prompt.md)
                 - message
                     - [MesopGUIMessageVisitor](api/fastagency/ui/mesop/message/MesopGUIMessageVisitor.md)
+                    - [consume_responses](api/fastagency/ui/mesop/message/consume_responses.md)
+                    - [handle_message](api/fastagency/ui/mesop/message/handle_message.md)
                     - [message_box](api/fastagency/ui/mesop/message/message_box.md)
                 - send_prompt
                     - [send_prompt_to_autogen](api/fastagency/ui/mesop/send_prompt/send_prompt_to_autogen.md)
diff --git a/docs/docs/en/api/fastagency/ui/mesop/data_model/ConversationMessage.md b/docs/docs/en/api/fastagency/ui/mesop/data_model/ConversationMessage.md
new file mode 100644
index 00000000..4c9c31e3
--- /dev/null
+++ b/docs/docs/en/api/fastagency/ui/mesop/data_model/ConversationMessage.md
@@ -0,0 +1,11 @@
+---
+# 0.5 - API
+# 2 - Release
+# 3 - Contributing
+# 5 - Template Page
+# 10 - Default
+search:
+  boost: 0.5
+---
+
+::: fastagency.ui.mesop.data_model.ConversationMessage
diff --git a/docs/docs/en/api/fastagency/ui/mesop/message/consume_responses.md b/docs/docs/en/api/fastagency/ui/mesop/message/consume_responses.md
new file mode 100644
index 00000000..09f6c44a
--- /dev/null
+++ b/docs/docs/en/api/fastagency/ui/mesop/message/consume_responses.md
@@ -0,0 +1,11 @@
+---
+# 0.5 - API
+# 2 - Release
+# 3 - Contributing
+# 5 - Template Page
+# 10 - Default
+search:
+  boost: 0.5
+---
+
+::: fastagency.ui.mesop.message.consume_responses
diff --git a/docs/docs/en/api/fastagency/ui/mesop/main/on_user_feedback.md b/docs/docs/en/api/fastagency/ui/mesop/message/handle_message.md
similarity index 70%
rename from docs/docs/en/api/fastagency/ui/mesop/main/on_user_feedback.md
rename to docs/docs/en/api/fastagency/ui/mesop/message/handle_message.md
index 5ead74b0..8b6c8763 100644
--- a/docs/docs/en/api/fastagency/ui/mesop/main/on_user_feedback.md
+++ b/docs/docs/en/api/fastagency/ui/mesop/message/handle_message.md
@@ -8,4 +8,4 @@ search:
   boost: 0.5
 ---
 
-::: fastagency.ui.mesop.main.on_user_feedback
+::: fastagency.ui.mesop.message.handle_message
diff --git a/docs/docs_src/user_guide/custom_user_interactions/main.py b/docs/docs_src/user_guide/custom_user_interactions/main.py
index f248450c..e2904ea5 100644
--- a/docs/docs_src/user_guide/custom_user_interactions/main.py
+++ b/docs/docs_src/user_guide/custom_user_interactions/main.py
@@ -23,8 +23,8 @@ llm_config = {
 wf = AutoGenWorkflows()
 
 
-@wf.register(name="exam_practice", description="Student and teacher chat")  # type: ignore[type-var]
-def exam_learning(ui: UI, initial_message: str, session_id: str) -> Optional[str]:
+@wf.register(name="exam_practice", description="Student and teacher chat")
+def exam_learning(ui: UI, initial_message: str, session_id: str) -> str:
 
     def is_termination_msg(msg: dict[str, Any]) -> bool:
         return msg["content"] is not None and "TERMINATE" in msg["content"]
diff --git a/docs/docs_src/user_guide/custom_user_interactions/main_mesop.py b/docs/docs_src/user_guide/custom_user_interactions/main_mesop.py
new file mode 100644
index 00000000..6de815dd
--- /dev/null
+++ b/docs/docs_src/user_guide/custom_user_interactions/main_mesop.py
@@ -0,0 +1,136 @@
+import os
+from typing import Annotated, Any, Dict, Optional
+
+from autogen import register_function
+from autogen.agentchat import ConversableAgent
+
+from fastagency import FastAgency
+from fastagency import UI
+from fastagency.base import MultipleChoice, SystemMessage, TextInput
+from fastagency.ui.mesop import MesopUI
+from fastagency.runtime.autogen.base import AutoGenWorkflows
+
+llm_config = {
+    "config_list": [
+        {
+            "model": "gpt-4o-mini",
+            "api_key": os.getenv("OPENAI_API_KEY"),
+        }
+    ],
+    "temperature": 0.0,
+}
+
+wf = AutoGenWorkflows()
+
+
+@wf.register(name="exam_practice", description="Student and teacher chat")
+def exam_learning(ui: UI, initial_message: str, session_id: str) -> str:
+
+    def is_termination_msg(msg: dict[str, Any]) -> bool:
+        return msg["content"] is not None and "TERMINATE" in msg["content"]
+
+    student_agent = ConversableAgent(
+        name="Student_Agent",
+        system_message="You are a student writing a practice test. Your task is as follows:\n"
+        "  1) Retrieve exam questions by calling a function.\n"
+        "  2) Write a draft of proposed answers and engage in dialogue with your tutor.\n"
+        "  3) Once you are done with the dialogue, register the final answers by calling a function.\n"
+        "  4) Retrieve the final grade by calling a function.\n"
+        "Finally, terminate the chat by saying 'TERMINATE'.",
+        llm_config=llm_config,
+        human_input_mode="NEVER",
+        is_termination_msg=is_termination_msg,
+    )
+    teacher_agent = ConversableAgent(
+        name="Teacher_Agent",
+        system_message="You are a teacher.",
+        llm_config=llm_config,
+        human_input_mode="NEVER",
+        is_termination_msg=is_termination_msg,
+    )
+
+    def retrieve_exam_questions(
+        message: Annotated[str, "Message for examiner"]
+    ) -> Optional[str]:
+        try:
+            msg = MultipleChoice(
+                sender="student",
+                recipient="teacher",
+                prompt=message,
+                choices=[
+                    "1) Mona Lisa",
+                    "2) Innovations",
+                    "3) Florence at the time of Leonardo",
+                    "4) The Last Supper",
+                    "5) Vitruvian Man",
+                ],
+                default="1) Mona Lisa"
+            )
+            return ui.process_message(msg)
+        except Exception as e:
+            return f"retrieve_exam_questions() FAILED! {e}"
+
+    def write_final_answers(message: Annotated[str, "Message for examiner"]) -> str:
+        try:
+            msg = SystemMessage(
+                sender="function call logger",
+                recipient="system",
+                message={
+                    "operation": "storing final answers",
+                    "content": message,
+                },
+            )
+            ui.process_message(msg)
+            return "Final answers stored."
+        except Exception as e:
+            return f"write_final_answers() FAILED! {e}"
+
+    def get_final_grade(
+        message: Annotated[str, "Message for examiner"]
+    ) -> Optional[str]:
+        try:
+            msg = MultipleChoice(
+                sender="student",
+                recipient="teacher",
+                prompt=message,
+                choices=["A", "B", "C", "D", "F"],
+            )
+            return ui.process_message(msg)
+        except Exception as e:
+            return f"get_final_grade() FAILED! {e}"
+
+    register_function(
+        retrieve_exam_questions,
+        caller=student_agent,
+        executor=teacher_agent,
+        name="retrieve_exam_questions",
+        description="Get exam questions from examiner",
+    )
+
+    register_function(
+        write_final_answers,
+        caller=student_agent,
+        executor=teacher_agent,
+        name="write_final_answers",
+        description="Write a final answers to exam questions to examiner, but only after discussing with the tutor first.",
+    )
+
+    register_function(
+        get_final_grade,
+        caller=student_agent,
+        executor=teacher_agent,
+        name="get_final_grade",
+        description="Get the final grade after submitting the answers.",
+    )
+
+    chat_result = teacher_agent.initiate_chat(
+        student_agent,
+        message=initial_message,
+        summary_method="reflection_with_llm",
+        max_turns=10,
+    )
+
+    return chat_result.summary  # type: ignore[no-any-return]
+
+
+app = FastAgency(wf=wf, ui=MesopUI())
diff --git a/docs/docs_src/user_guide/external_rest_apis/main_mesop.py b/docs/docs_src/user_guide/external_rest_apis/main_mesop.py
new file mode 100644
index 00000000..4ba119b1
--- /dev/null
+++ b/docs/docs_src/user_guide/external_rest_apis/main_mesop.py
@@ -0,0 +1,60 @@
+import os
+
+from autogen import UserProxyAgent
+from autogen.agentchat import ConversableAgent
+
+from fastagency import FastAgency
+from fastagency import UI
+from fastagency.ui.mesop import MesopUI
+from fastagency.runtime.autogen.base import AutoGenWorkflows
+
+from fastagency.api.openapi import OpenAPI
+
+
+llm_config = {
+    "config_list": [
+        {
+            "model": "gpt-4o-mini",
+            "api_key": os.getenv("OPENAI_API_KEY"),
+        }
+    ],
+    "temperature": 0.0,
+}
+
+WEATHER_OPENAPI_URL = "https://weather.tools.fastagency.ai/openapi.json"
+
+wf = AutoGenWorkflows()
+
+
+@wf.register(name="simple_weather", description="Weather chat")
+def weather_workflow(ui: UI, initial_message: str, session_id: str) -> str:
+
+    weather_api = OpenAPI.create(openapi_url=WEATHER_OPENAPI_URL)
+
+    user_agent = UserProxyAgent(
+        name="User_Agent",
+        system_message="You are a user agent",
+        llm_config=llm_config,
+        human_input_mode="NEVER",
+    )
+    weather_agent = ConversableAgent(
+        name="Weather_Agent",
+        system_message="You are a weather agent",
+        llm_config=llm_config,
+        human_input_mode="NEVER",
+    )
+
+    weather_api.register_for_llm(weather_agent)
+    weather_api.register_for_execution(user_agent)
+
+    chat_result = user_agent.initiate_chat(
+        weather_agent,
+        message=initial_message,
+        summary_method="reflection_with_llm",
+        max_turns=3,
+    )
+
+    return chat_result.summary  # type: ignore[no-any-return]
+
+
+app = FastAgency(wf=wf, ui=MesopUI())
diff --git a/fastagency/studio/models/agents/assistant.py b/fastagency/studio/models/agents/assistant.py
index c6f816ed..b323744b 100644
--- a/fastagency/studio/models/agents/assistant.py
+++ b/fastagency/studio/models/agents/assistant.py
@@ -16,7 +16,7 @@ class AssistantAgent(AgentBaseModel):
         Field(
             description="The system message of the agent. This message is used to inform the agent about his role in the conversation"
         ),
-    ] = "You are a helpful assistant. After you successfully answer all questions and there are no new questions asked after your response (e.g. there is no specific direction or question asked after you give a response), terminate the chat by outputting 'TERMINATE'"
+    ] = "You are a helpful assistant. After you successfully answer all questions and there are no new questions asked after your response (e.g. there is no specific direction or question asked after you give a response), terminate the chat by outputting 'TERMINATE' (IMPORTANT: use all caps)"
 
     @classmethod
     async def create_autogen(
diff --git a/fastagency/ui/mesop/base.py b/fastagency/ui/mesop/base.py
index 837abe42..53c01c43 100644
--- a/fastagency/ui/mesop/base.py
+++ b/fastagency/ui/mesop/base.py
@@ -221,6 +221,19 @@ def run_workflow(wf: Workflows, name: str, initial_message: str) -> MesopUI:
                 },
             )
         )
+
+        # io.process_message(
+        #    IOMessage.create(
+        #        sender="tester",
+        #        recipient="workflow",
+        #        type="multiple_choice",
+        #        prompt="Concentrate and choose correct answer. When are you going to write unit tests?",
+        #        choices=["Today", "Tomorrow", "Never", "I already have unit tests"],
+        #        default="Tomorrow",
+        #        single=True,
+        #    )
+        # )
+
         try:
             result = wf.run(
                 name=name,
@@ -228,6 +241,7 @@ def run_workflow(wf: Workflows, name: str, initial_message: str) -> MesopUI:
                 ui=subconversation,  # type: ignore[arg-type]
                 initial_message=initial_message,
             )
+
         except Exception as ex:
             ui.process_message(
                 IOMessage.create(
diff --git a/fastagency/ui/mesop/components/inputs.py b/fastagency/ui/mesop/components/inputs.py
index 2f05fe81..36269f29 100644
--- a/fastagency/ui/mesop/components/inputs.py
+++ b/fastagency/ui/mesop/components/inputs.py
@@ -12,8 +12,12 @@ def _on_blur(e: me.InputBlurEvent) -> None:
 
 
 def input_user_feedback(
-    send_feedback: Callable[[me.ClickEvent], Iterator[None]],
+    send_feedback: Callable[[me.ClickEvent], Iterator[None]], disabled: bool = False
 ) -> None:
+    def _on_feedback_blur(e: me.InputBlurEvent) -> None:
+        state = me.state(State)
+        state.conversation.feedback = e.value
+
     with me.box(
         style=me.Style(
             border_radius=16,
@@ -26,8 +30,8 @@ def input_user_feedback(
         with me.box(style=me.Style(flex_grow=1)):
             me.native_textarea(
                 placeholder="Provide a feedback to the team",
-                on_blur=_on_blur,
-                key="feedback",
+                on_blur=_on_feedback_blur,
+                disabled=disabled,
                 style=me.Style(
                     padding=me.Padding(top=16, left=16),
                     outline="none",
@@ -35,7 +39,11 @@ def input_user_feedback(
                     border=me.Border.all(me.BorderSide(style="none")),
                 ),
             )
-        with me.content_button(type="icon", on_click=send_feedback):
+        with me.content_button(
+            type="icon",
+            on_click=send_feedback,
+            disabled=disabled,
+        ):
             me.icon("send")
 
 
diff --git a/fastagency/ui/mesop/data_model.py b/fastagency/ui/mesop/data_model.py
index f1563c3d..7110a907 100644
--- a/fastagency/ui/mesop/data_model.py
+++ b/fastagency/ui/mesop/data_model.py
@@ -4,6 +4,14 @@ from typing import Optional
 import mesop as me
 
 
+@dataclass
+class ConversationMessage:
+    io_message_json: str = ""
+    level: int = 0
+    conversation_id: str = ""
+    feedback: list[str] = field(default_factory=list)
+
+
 @dataclass
 class Conversation:
     id: str = ""
@@ -12,8 +20,7 @@ class Conversation:
     waiting_for_feedback: bool = False
     feedback: str = ""
     is_from_the_past: bool = False
-    # messages: list[ConversationMessage] = field(default_factory=list)
-    messages: list[str] = field(default_factory=list)
+    messages: list[ConversationMessage] = field(default_factory=list)
     fastagency: Optional[str] = None
 
 
@@ -24,4 +31,3 @@ class State:
     conversation: Conversation
     past_conversations: list[Conversation] = field(default_factory=list)
     hide_past: bool = True
-    fastagency: Optional[str] = None
diff --git a/fastagency/ui/mesop/main.py b/fastagency/ui/mesop/main.py
index 815e432a..b489d5a7 100644
--- a/fastagency/ui/mesop/main.py
+++ b/fastagency/ui/mesop/main.py
@@ -1,17 +1,14 @@
-import json
 from collections.abc import Iterator
-from uuid import uuid4
 
 import mesop as me
 
-from ...base import AskingMessage, WorkflowCompleted
 from ...logging import get_logger
-from .base import MesopMessage, MesopUI
-from .components.inputs import input_prompt, input_user_feedback
+from .base import MesopUI
+from .components.inputs import input_prompt
 from .components.ui_common import header
 from .data_model import Conversation, State
-from .message import message_box
-from .send_prompt import send_prompt_to_autogen, send_user_feedback_to_autogen
+from .message import consume_responses, message_box
+from .send_prompt import send_prompt_to_autogen
 from .styles import (
     CHAT_STARTER_STYLE,
     PAST_CHATS_HIDE_STYLE,
@@ -114,37 +111,6 @@ def conversation_starter_box() -> None:
             input_prompt(send_prompt)
 
 
-def _handle_message(state: State, message: MesopMessage) -> None:
-    conversation = state.conversation
-    messages = conversation.messages
-    level = message.conversation.level
-    conversation_id = message.conversation.id
-    io_message = message.io_message
-    message_dict = io_message.model_dump()
-    message_string = json.dumps(
-        {"level": level, "conversationId": conversation_id, "io_message": message_dict}
-    )
-    messages.append(message_string)
-    conversation.messages = list(messages)
-    if isinstance(io_message, AskingMessage):
-        conversation.waiting_for_feedback = True
-        conversation.completed = False
-    if isinstance(io_message, WorkflowCompleted):
-        conversation.completed = True
-        conversation.waiting_for_feedback = False
-        if not conversation.is_from_the_past:
-            uuid: str = uuid4().hex
-            becomme_past = Conversation(
-                id=uuid,
-                title=conversation.title,
-                messages=conversation.messages,
-                completed=True,
-                is_from_the_past=True,
-                waiting_for_feedback=False,
-            )
-            state.past_conversations.insert(0, becomme_past)
-
-
 def send_prompt(e: me.ClickEvent) -> Iterator[None]:
     ui = get_ui()
     wf = ui.app.wf
@@ -162,13 +128,7 @@ def send_prompt(e: me.ClickEvent) -> Iterator[None]:
     state.in_conversation = True
     yield
     responses = send_prompt_to_autogen(prompt=prompt, wf=wf, name=name)
-    for message in responses:
-        state = me.state(State)
-        _handle_message(state, message)
-        yield
-        me.scroll_into_view(key="end_of_messages")
-        yield
-    yield
+    yield from consume_responses(responses)
 
 
 def conversation_box() -> None:
@@ -183,30 +143,9 @@ def conversation_box() -> None:
             )
         ):
             for message in messages:
-                message_box(message)
+                message_box(message, conversation.is_from_the_past)
             if messages:
                 me.box(
                     key="end_of_messages",
                     style=me.Style(margin=me.Margin(bottom="50vh")),
                 )
-        if conversation.waiting_for_feedback:
-            input_user_feedback(on_user_feedback)
-
-
-def on_user_feedback(e: me.ClickEvent) -> Iterator[None]:
-    state = me.state(State)
-    conversation = state.conversation
-    feedback = conversation.feedback
-    conversation.feedback = ""
-    conversation.waiting_for_feedback = False
-    yield
-    me.scroll_into_view(key="end_of_messages")
-    yield
-    responses = send_user_feedback_to_autogen(feedback)
-    for message in responses:
-        state = me.state(State)
-        _handle_message(state, message)
-        yield
-        me.scroll_into_view(key="end_of_messages")
-        yield
-    yield
diff --git a/fastagency/ui/mesop/message.py b/fastagency/ui/mesop/message.py
index e7ff75cf..00e31542 100644
--- a/fastagency/ui/mesop/message.py
+++ b/fastagency/ui/mesop/message.py
@@ -1,8 +1,15 @@
 import json
+from collections.abc import Iterable, Iterator
 from typing import Optional
+from uuid import uuid4
 
 import mesop as me
 
+from fastagency.base import AskingMessage, WorkflowCompleted
+from fastagency.ui.mesop.base import MesopMessage
+from fastagency.ui.mesop.components.inputs import input_user_feedback
+from fastagency.ui.mesop.send_prompt import send_user_feedback_to_autogen
+
 from ...base import (
     IOMessage,
     IOMessageVisitor,
@@ -12,28 +19,97 @@ from ...base import (
     TextMessage,
 )
 from .components.ui_common import darken_hex_color
+from .data_model import Conversation, ConversationMessage, State
+
 
+def consume_responses(responses: Iterable[MesopMessage]) -> Iterator[None]:
+    for message in responses:
+        state = me.state(State)
+        handle_message(state, message)
+        yield
+        me.scroll_into_view(key="end_of_messages")
+        yield
+    yield
 
-def message_box(message: str) -> None:
-    message_dict = json.loads(message)
-    level = message_dict["level"]
-    conversation_id = message_dict["conversationId"]
-    io_message_dict = message_dict["io_message"]
+
+def message_box(message: ConversationMessage, read_only: bool) -> None:
+    io_message_dict = json.loads(message.io_message_json)
+    level = message.level
+    conversation_id = message.conversation_id
     io_message = IOMessage.create(**io_message_dict)
-    visitor = MesopGUIMessageVisitor(level, conversation_id)
+    visitor = MesopGUIMessageVisitor(level, conversation_id, message, read_only)
     visitor.process_message(io_message)
 
 
+def handle_message(state: State, message: MesopMessage) -> None:
+    conversation = state.conversation
+    messages = conversation.messages
+    level = message.conversation.level
+    conversation_id = message.conversation.id
+    io_message = message.io_message
+    message_dict = io_message.model_dump()
+    message_json = json.dumps(message_dict)
+    conversation_message = ConversationMessage(
+        level=level,
+        conversation_id=conversation_id,
+        io_message_json=message_json,
+        feedback=[],
+    )
+    messages.append(conversation_message)
+    conversation.messages = list(messages)
+    if isinstance(io_message, AskingMessage):
+        conversation.waiting_for_feedback = True
+        conversation.completed = False
+    if isinstance(io_message, WorkflowCompleted):
+        conversation.completed = True
+        conversation.waiting_for_feedback = False
+        if not conversation.is_from_the_past:
+            uuid: str = uuid4().hex
+            becomme_past = Conversation(
+                id=uuid,
+                title=conversation.title,
+                messages=conversation.messages,
+                completed=True,
+                is_from_the_past=True,
+                waiting_for_feedback=False,
+            )
+            state.past_conversations.insert(0, becomme_past)
+
+
 class MesopGUIMessageVisitor(IOMessageVisitor):
-    def __init__(self, level: int, conversation_id: str) -> None:
+    def __init__(
+        self,
+        level: int,
+        conversation_id: str,
+        conversation_message: ConversationMessage,
+        read_only: bool = False,
+    ) -> None:
         """Initialize the MesopGUIMessageVisitor object.
 
         Args:
             level (int): The level of the message.
             conversation_id (str): The ID of the conversation.
+            conversation_message (ConversationMessage): Conversation message that wraps the visited io_message
+            read_only (bool): Input messages are disabled in read only mode
         """
         self._level = level
         self._conversation_id = conversation_id
+        self._readonly = read_only
+        self._conversation_message = conversation_message
+
+    def _has_feedback(self) -> bool:
+        return len(self._conversation_message.feedback) > 0
+
+    def _provide_feedback(self, feedback: str) -> Iterator[None]:
+        state = me.state(State)
+        conversation = state.conversation
+        conversation.feedback = ""
+        conversation.waiting_for_feedback = False
+        yield
+        me.scroll_into_view(key="end_of_messages")
+        yield
+        responses = send_user_feedback_to_autogen(feedback)
+        yield from consume_responses(responses)
 
     def visit_default(self, message: IOMessage) -> None:
         base_color = "#aff"
@@ -75,12 +151,18 @@ class MesopGUIMessageVisitor(IOMessageVisitor):
             me.markdown(json.dumps(message.message, indent=2))
 
     def visit_text_input(self, message: TextInput) -> str:
-        text = message.prompt if message.prompt else "Please enter a value"
+        def on_input(ev: me.RadioChangeEvent) -> Iterator[None]:
+            state = me.state(State)
+            feedback = state.conversation.feedback
+            self._conversation_message.feedback = [feedback]
+            yield from self._provide_feedback(feedback)
+
+        base_color = "#dff"
+        prompt = message.prompt if message.prompt else "Please enter a value"
         if message.suggestions:
             suggestions = ",".join(suggestion for suggestion in message.suggestions)
-            text += "\n" + suggestions
+            prompt += "\n" + suggestions
 
-        base_color = "#bff"
         with me.box(
             style=me.Style(
                 background=base_color,
@@ -90,17 +172,32 @@ class MesopGUIMessageVisitor(IOMessageVisitor):
             )
         ):
             self._header(message, base_color, title="Input requested")
-            me.markdown(text)
+            me.markdown(prompt)
+            input_user_feedback(
+                on_input, disabled=self._readonly or self._has_feedback()
+            )
         return ""
 
     def visit_multiple_choice(self, message: MultipleChoice) -> str:
-        text = message.prompt if message.prompt else "Please enter a value"
+        def on_change(ev: me.RadioChangeEvent) -> Iterator[None]:
+            feedback = ev.value
+            self._conversation_message.feedback = [feedback]
+            yield from self._provide_feedback(feedback)
+
+        base_color = "#dff"
+        prompt = message.prompt if message.prompt else "Please enter a value"
         if message.choices:
-            options = ",".join(
-                f"{i+1}. {choice}" for i, choice in enumerate(message.choices)
+            options = (
+                me.RadioOption(
+                    label=(choice if choice != message.default else choice + " *"),
+                    value=choice,
+                )
+                for choice in message.choices
             )
-            text += "\n" + options
-        base_color = "#cff"
+        if self._has_feedback():
+            pre_selected = {"value": self._conversation_message.feedback[0]}
+        else:
+            pre_selected = {}
         with me.box(
             style=me.Style(
                 background=base_color,
@@ -110,7 +207,14 @@ class MesopGUIMessageVisitor(IOMessageVisitor):
             )
         ):
             self._header(message, base_color, title="Input requested")
-            me.markdown(text)
+            me.text(prompt)
+            me.radio(
+                on_change=on_change,
+                disabled=self._readonly or self._has_feedback(),
+                options=options,
+                style=me.Style(display="flex", flex_direction="column"),
+                **pre_selected,
+            )
         return ""
 
     def process_message(self, message: IOMessage) -> Optional[str]:
diff --git a/fastagency/ui/mesop/send_prompt.py b/fastagency/ui/mesop/send_prompt.py
index e21961f2..be22f02e 100644
--- a/fastagency/ui/mesop/send_prompt.py
+++ b/fastagency/ui/mesop/send_prompt.py
@@ -18,7 +18,7 @@ def send_prompt_to_autogen(
 
 def send_user_feedback_to_autogen(user_response: str) -> Iterable[MesopMessage]:
     state = me.state(State)
-    mesop_id = state.fastagency
+    mesop_id = state.conversation.fastagency
     mesop_io = MesopUI.get_conversation(mesop_id)
     mesop_io.respond(user_response)
     return mesop_io.get_message_stream()


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 6c716442..b812a369 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -619,7 +619,7 @@ async def placeholder_assistant_weatherapi_ref(
         name=add_random_suffix("assistant_weather"),
         llm=llm_ref,
         toolbox_1=weather_toolbox_ref,
-        system_message="You are a helpful assistant with access to Weather API. After you successfully answer the question asked and there are no new questions, terminate the chat by outputting 'TERMINATE' (all caps)",
+        system_message="You are a helpful assistant with access to Weather API. After you successfully answer the question asked and there are no new questions, terminate the chat by outputting 'TERMINATE' (in all caps, e.g.'Terminate' will be ignored).",
     )
 
 
diff --git a/tests/studio/models/agents/test_assistant.py b/tests/studio/models/agents/test_assistant.py
index ba3a6c9d..fdba7780 100644
--- a/tests/studio/models/agents/test_assistant.py
+++ b/tests/studio/models/agents/test_assistant.py
@@ -209,7 +209,7 @@ class TestAssistantAgent:
                     "title": "Toolbox",
                 },
                 "system_message": {
-                    "default": "You are a helpful assistant. After you successfully answer all questions and there are no new questions asked after your response (e.g. there is no specific direction or question asked after you give a response), terminate the chat by outputting 'TERMINATE'",
+                    "default": "You are a helpful assistant. After you successfully answer all questions and there are no new questions asked after your response (e.g. there is no specific direction or question asked after you give a response), terminate the chat by outputting 'TERMINATE' (IMPORTANT: use all caps)",
                     "description": "The system message of the agent. This message is used to inform the agent about his role in the conversation",
                     "title": "System Message",
                     "type": "string",
diff --git a/tests/studio/models/teams/test_two_agents_team.py b/tests/studio/models/teams/test_two_agents_team.py
index 67e181f1..5a8689a3 100644
--- a/tests/studio/models/teams/test_two_agents_team.py
+++ b/tests/studio/models/teams/test_two_agents_team.py
@@ -204,7 +204,8 @@ class TestTwoAgentTeamSimpleChat:
 
         assert ag_team
         history = ag_team.initiate_chat("What is 2 + 2?")
-        assert sum(["TERMINATE" in msg["content"] for msg in history.chat_history]) == 1
+        messages = (msg["content"] for msg in history.chat_history)
+        assert sum("TERMINATE" in msg for msg in messages) == 1
 
     @pytest.mark.asyncio
     @parametrize_fixtures("team_ref", get_by_tag("team", "weather"))