diff --git a/README.md b/README.md
index df63e855d0fd28a21427e12b7acb617f519f29f2..9b1df80539bf4f2208aa023f8504dc4f536f47bc 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,127 @@
----
-title: OmAgent
-emoji: 🦀
-colorFrom: yellow
-colorTo: red
-sdk: gradio
-sdk_version: 5.16.2
-app_file: app.py
-pinned: false
----
-
-Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
+# Video Understanding Example
+
+This example demonstrates how to use the framework for hour-long video understanding task. The example code can be found in the `examples/video_understanding` directory.
+
+```bash
+   cd examples/video_understanding
+```
+
+## Overview
+
+This example implements a video understanding task workflow based on the DnC workflow, which consists of following components:
+
+1. **Video Preprocess Task**
+   - Preprocess the video with audio information via speech-to-text capability
+   - It detects the scene boundaries, splits the video into several chunks and extract frames at specified intervals
+   - Each scene chunk is summarized by MLLM with detailed information, cached and updated into vector database for Q&A retrieval
+   - Video metadata and video file md5 are transferred for filtering
+
+2. **Video QA Task**
+   - Take the user input question about the video
+   - Retrieve related information from the vector database with the question
+   - Extract the approximate start and end time of the video segment related to the question
+   - Generate video object from serialized data in short-term memory(stm)
+   - Build init task tree with the question to DnC task
+
+3. **Divide and Conquer Task**
+   - Execute the task tree with the question
+   - Detailed information is referred to the [DnC Example](./DnC.md#overview)
+
+The system uses Redis for state management, Milvus for long-tern memory storage and Conductor for workflow orchestration.
+
+### This whole workflow is looked like the following diagram:
+
+![Video Understanding Workflow](docsmages/video_understanding_workflow_diagram.png)
+
+## Prerequisites
+
+- Python 3.10+
+- Required packages installed (see requirements.txt)
+- Access to OpenAI API or compatible endpoint (see configs/llms/*.yml)
+- [Optional] Access to Bing API for WebSearch tool (see configs/tools/*.yml)
+- Redis server running locally or remotely
+- Conductor server running locally or remotely
+
+## Configuration
+
+The container.yaml file is a configuration file that manages dependencies and settings for different components of the system, including Conductor connections, Redis connections, and other service configurations. To set up your configuration:
+
+1. Generate the container.yaml file:
+   ```bash
+   python compile_container.py
+   ```
+   This will create a container.yaml file with default settings under `examples/video_understanding`.
+
+
+2. Configure your LLM and tool settings in `configs/llms/*.yml` and `configs/tools/*.yml`:
+   - Set your OpenAI API key or compatible endpoint through environment variable or by directly modifying the yml file
+   ```bash
+   export custom_openai_key="your_openai_api_key"
+   export custom_openai_endpoint="your_openai_endpoint"
+   ```
+   - [Optional] Set your Bing API key or compatible endpoint through environment variable or by directly modifying the yml file
+   ```bash
+   export bing_api_key="your_bing_api_key"
+   ```
+   **Note: It isn't mandatory to set the Bing API key, as the WebSearch tool will rollback to use duckduckgo search. But it is recommended to set it for better search quality.**
+   - The default text encoder configuration uses OpenAI `text-embedding-3-large` with **3072** dimensions, make sure you change the dim value of `MilvusLTM` in `container.yaml`
+   - Configure other model settings like temperature as needed through environment variable or by directly modifying the yml file
+
+3. Update settings in the generated `container.yaml`:
+   - Modify Redis connection settings:
+     - Set the host, port and credentials for your Redis instance
+     - Configure both `redis_stream_client` and `redis_stm_client` sections
+   - Update the Conductor server URL under conductor_config section
+   - Configure MilvusLTM in `components` section:
+     - Set the `storage_name` and `dim` for MilvusLTM
+     - Set `dim` is to **3072** if you use default OpenAI encoder, make sure to modify corresponding dimension if you use other custom text encoder model or endpoint 
+     - Adjust other settings as needed
+   - Configure hyper-parameters for video preprocess task in `examples/video_understanding/configs/workers/video_preprocessor.yml`
+     - `use_cache`: Whether to use cache for the video preprocess task
+     - `scene_detect_threshold`: The threshold for scene detection, which is used to determine if a scene change occurs in the video, min value means more scenes will be detected, default value is **27**
+     - `frame_extraction_interval`: The interval between frames to extract from the video, default value is **5**
+     - `kernel_size`: The size of the kernel for scene detection, should be **odd** number, default value is automatically calculated based on the resolution of the video. For hour-long videos, it is recommended to leave it blank, but for short videos, it is recommended to set a smaller value, like **3**, **5** to make it more sensitive to the scene change
+     - `stt.endpoint`: The endpoint for the speech-to-text service, default uses OpenAI ASR service
+     - `stt.api_key`: The API key for the speech-to-text service, default uses OpenAI API key
+   - Adjust any other component settings as needed
+
+## Running the Example
+
+1. Run the video understanding example via Webpage:
+
+   ```bash
+   python run_webpage.py
+   ```
+
+   First, select a video or upload a video file on the left; after the video preprocessing is completed, ask questions about the video content on the right.
+
+
+2. Run the video understanding example, currently only supports CLI usage:
+
+   ```bash
+   python run_cli.py
+   ```
+
+   First time you need to input the video file path, it will take a while to preprocess the video and store the information into vector database.
+   After the video is preprocessed, you can input your question about the video and the system will answer it. Note that the agent may give the wrong or vague answer, especially some questions are related the name of the characters in the video.
+
+## Troubleshooting
+
+If you encounter issues:
+- Verify Redis is running and accessible
+- Try smaller `scene_detect_threshold` and `frame_extraction_interval` if you find too many scenes are detected
+- Check your OpenAI API key is valid
+- Check your Bing API key is valid if search results are not as expected
+- Check the `dim` value in `MilvusLTM` in `container.yaml` is set correctly, currently unmatched dimension setting will not raise error but lose partial of the information(we will add more checks in the future)
+- Ensure all dependencies are installed correctly
+- Review logs for any error messages
+- **Open an issue on GitHub if you can't find a solution, we will do our best to help you out!**
+
+
+4. Run the video understanding example, currently only supports Webpage usage:
+
+   ```bash
+   python run_webpage.py
+   ```
+
+   First, select a video or upload a video file on the left; after the video preprocessing is completed, ask questions about the video content on the right.
diff --git a/agent/__init__.py b/agent/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/agent/conclude/__init__.py b/agent/conclude/__init__.py
new file mode 100755
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/agent/conclude/conclude.py b/agent/conclude/conclude.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a66572ff50ee4c700b3c5730d391e4b0c12de70
--- /dev/null
+++ b/agent/conclude/conclude.py
@@ -0,0 +1,87 @@
+from pathlib import Path
+from typing import List
+
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import \
+    TaskTree
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.memories.ltms.ltm import LTM
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+from collections.abc import Iterator
+from pydantic import Field
+
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class Conclude(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def _run(self, dnc_structure: dict, last_output: str, *args, **kwargs):
+        """A conclude node that summarizes and completes the root task.
+
+        This component acts as the final node that:
+        - Takes the root task and its execution results
+        - Generates a final conclusion/summary of the entire task execution
+        - Formats and presents the final output in a clear way
+        - Cleans up any temporary state/memory used during execution
+
+        The conclude node is responsible for providing a coherent final response that
+        addresses the original root task objective based on all the work done by
+        previous nodes.
+
+        Args:
+            agent_task (dict): The task tree containing the root task and results
+            last_output (str): The final output from previous task execution
+            *args: Additional arguments
+            **kwargs: Additional keyword arguments
+
+        Returns:
+            dict: Final response containing the conclusion/summary
+        """
+        task = TaskTree(**dnc_structure)
+        self.callback.info(
+            agent_id=self.workflow_instance_id,
+            progress=f"Conclude",
+            message=f"{task.get_current_node().task}",
+        )
+        chat_complete_res = self.simple_infer(
+            task=task.get_root().task,
+            result=str(last_output),
+            img_placeholders="".join(
+                list(self.stm(self.workflow_instance_id).get("image_cache", {}).keys())
+            ),
+        )
+        if isinstance(chat_complete_res, Iterator):
+            last_output = "Answer: "
+            self.callback.send_incomplete(
+                agent_id=self.workflow_instance_id, msg="Answer: "
+            )
+            for chunk in chat_complete_res:
+                if len(chunk.choices) > 0:
+                    current_msg = chunk.choices[0].delta.content if chunk.choices[0].delta.content is not None else ''
+                    self.callback.send_incomplete(
+                        agent_id=self.workflow_instance_id,
+                        msg=f"{current_msg}",
+                    )
+                    last_output += current_msg
+            self.callback.send_answer(agent_id=self.workflow_instance_id, msg="")
+        else:
+            last_output = chat_complete_res["choices"][0]["message"]["content"]
+            self.callback.send_answer(
+                agent_id=self.workflow_instance_id,
+                msg=f'Answer: {chat_complete_res["choices"][0]["message"]["content"]}',
+            )
+        self.stm(self.workflow_instance_id).clear()
+        return {"last_output": last_output}
diff --git a/agent/conclude/sys_prompt.prompt b/agent/conclude/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..f28aef3d6f6a4f13a0468cd275bec95881bc60e9
--- /dev/null
+++ b/agent/conclude/sys_prompt.prompt
@@ -0,0 +1,13 @@
+As the final stage of our task processing workflow, your role is to inform the user about the final execution result of the task.
+Your task includes two parts:
+1. Verify the result, ensure it is a valid result of the user's question or task.
+2. Image may be visual prompted by adding bound boxes and labels to the image, this is the important information.
+3. Generate the output message since you may get some raw data, you have to get the useful information and generate a detailed message.
+
+The task may complete successfully or it can be failed for some reason. You just need to honestly express the situation.
+
+*** Important Notice ***
+1. Please use the language used in the question when responding.
+2. Your response MUST be based on the results provided to you. Do not attempt to solve the problem on your own or try to correct any errors.
+3. Do not mention your source of information. Present the response as if it were your own.
+4. Handle the conversions between different units carefully.
\ No newline at end of file
diff --git a/agent/conclude/user_prompt.prompt b/agent/conclude/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..4d3f333bd686c793b00c1594dcdf320a2ec74e86
--- /dev/null
+++ b/agent/conclude/user_prompt.prompt
@@ -0,0 +1,7 @@
+Now, it's your turn to complete the task.
+
+Task (The task you need to complete.): {{task}}
+result (The result from former agents.): {{result}}
+images: {{img_placeholders}}
+
+Now show your super capability as a super agent that beyond regular AIs or LLMs!
\ No newline at end of file
diff --git a/agent/conclude/webpage_conclude.py b/agent/conclude/webpage_conclude.py
new file mode 100644
index 0000000000000000000000000000000000000000..c125c0964771155463ed4d9a59bbf9afc664b916
--- /dev/null
+++ b/agent/conclude/webpage_conclude.py
@@ -0,0 +1,81 @@
+from pathlib import Path
+from typing import Iterator, List
+
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import \
+    TaskTree
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.memories.ltms.ltm import LTM
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+from openai import Stream
+from pydantic import Field
+
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class WebpageConclude(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def _run(self, dnc_structure: dict, last_output: str, *args, **kwargs):
+        """A conclude node that summarizes and completes the root task.
+
+        This component acts as the final node that:
+        - Takes the root task and its execution results
+        - Generates a final conclusion/summary of the entire task execution
+        - Formats and presents the final output in a clear way
+        - Cleans up any temporary state/memory used during execution
+
+        The conclude node is responsible for providing a coherent final response that
+        addresses the original root task objective based on all the work done by
+        previous nodes.
+
+        Args:
+            agent_task (dict): The task tree containing the root task and results
+            last_output (str): The final output from previous task execution
+            *args: Additional arguments
+            **kwargs: Additional keyword arguments
+
+        Returns:
+            dict: Final response containing the conclusion/summary
+        """
+        task = TaskTree(**dnc_structure)
+        self.callback.info(
+            agent_id=self.workflow_instance_id,
+            progress=f"Conclude",
+            message=f"{task.get_current_node().task}",
+        )
+        chat_complete_res = self.simple_infer(
+            task=task.get_root().task,
+            result=str(last_output),
+            img_placeholders="".join(
+                list(self.stm(self.workflow_instance_id).get("image_cache", {}).keys())
+            ),
+        )
+        if isinstance(chat_complete_res, Iterator):
+            last_output = "Answer: "
+            for chunk in chat_complete_res:
+                if len(chunk.choices) > 0:
+                    current_msg = chunk.choices[0].delta.content if chunk.choices[0].delta.content is not None else ''
+                    last_output += current_msg
+            self.callback.send_answer(agent_id=self.workflow_instance_id, msg=last_output)
+        else:
+            last_output = chat_complete_res["choices"][0]["message"]["content"]
+            self.callback.send_answer(
+                agent_id=self.workflow_instance_id,
+                msg=f'Answer: {chat_complete_res["choices"][0]["message"]["content"]}',
+            )
+        self.callback.send_answer(agent_id=self.workflow_instance_id, msg=f"Token usage: {self.token_usage}")
+        self.stm(self.workflow_instance_id).clear()
+        return {"last_output": last_output}
diff --git a/agent/memories/__init__.py b/agent/memories/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/agent/memories/video_ltm_milvus.py b/agent/memories/video_ltm_milvus.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2178ec2557f37660f7108b005d96c4f323e4119
--- /dev/null
+++ b/agent/memories/video_ltm_milvus.py
@@ -0,0 +1,238 @@
+import base64
+import pickle
+from typing import Any, Iterable, List, Optional, Tuple
+
+from omagent_core.memories.ltms.ltm_base import LTMBase
+from omagent_core.services.connectors.milvus import MilvusConnector
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from pymilvus import (Collection, CollectionSchema, DataType, FieldSchema,
+                      utility)
+
+
+@registry.register_component()
+class VideoMilvusLTM(LTMBase):
+    milvus_ltm_client: MilvusConnector
+    storage_name: str = Field(default="default")
+    dim: int = Field(default=128)
+
+    def model_post_init(self, __context: Any) -> None:
+        pass
+
+    def _create_collection(self) -> None:
+        # Check if collection exists
+        if not self.milvus_ltm_client._client.has_collection(self.storage_name):
+            index_params = self.milvus_ltm_client._client.prepare_index_params()
+            # Define field schemas
+            key_field = FieldSchema(
+                name="key", dtype=DataType.VARCHAR, is_primary=True, max_length=256
+            )
+            value_field = FieldSchema(
+                name="value", dtype=DataType.JSON, description="Json value"
+            )
+            embedding_field = FieldSchema(
+                name="embedding",
+                dtype=DataType.FLOAT_VECTOR,
+                description="Embedding vector",
+                dim=self.dim,
+            )
+            index_params = self.milvus_ltm_client._client.prepare_index_params()
+
+            # Create collection schema
+            schema = CollectionSchema(
+                fields=[key_field, value_field, embedding_field],
+                description="Key-Value storage with embeddings",
+            )
+            for field in schema.fields:
+                if (
+                    field.dtype == DataType.FLOAT_VECTOR
+                    or field.dtype == DataType.BINARY_VECTOR
+                ):
+                    index_params.add_index(
+                        field_name=field.name,
+                        index_name=field.name,
+                        index_type="FLAT",
+                        metric_type="COSINE",
+                        params={"nlist": 128},
+                    )
+            self.milvus_ltm_client._client.create_collection(
+                self.storage_name, schema=schema, index_params=index_params
+            )
+
+            # Create index separately after collection creation
+            print(f"Created storage {self.storage_name} successfully")
+
+    def __getitem__(self, key: Any) -> Any:
+        key_str = str(key)
+        expr = f'key == "{key_str}"'
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["value"]
+        )
+        if res:
+            value = res[0]["value"]
+            # value_bytes = base64.b64decode(value_base64)
+            # value = pickle.loads(value_bytes)
+            return value
+        else:
+            raise KeyError(f"Key {key} not found")
+
+    def __setitem__(self, key: Any, value: Any) -> None:
+        self._create_collection()
+
+        key_str = str(key)
+
+        # Check if value is a dictionary containing 'value' and 'embedding'
+        if isinstance(value, dict) and "value" in value and "embedding" in value:
+            actual_value = value["value"]
+            embedding = value["embedding"]
+        else:
+            raise ValueError(
+                "When setting an item, value must be a dictionary containing 'value' and 'embedding' keys."
+            )
+
+        # Serialize the actual value and encode it to base64
+        # value_bytes = pickle.dumps(actual_value)
+        # value_base64 = base64.b64encode(value_bytes).decode('utf-8')
+
+        # Ensure the embedding is provided
+        if embedding is None:
+            raise ValueError("An embedding vector must be provided.")
+
+        # Check if the key exists and delete it if it does
+        if key_str in self:
+            self.__delitem__(key_str)
+
+        # Prepare data for insertion (as a list of dictionaries)
+        data = [
+            {
+                "key": key_str,
+                "value": actual_value,
+                "embedding": embedding,
+            }
+        ]
+
+        # Insert the new record
+        self.milvus_ltm_client._client.insert(
+            collection_name=self.storage_name, data=data
+        )
+
+    def __delitem__(self, key: Any) -> None:
+        key_str = str(key)
+        if key_str in self:
+            expr = f'key == "{key_str}"'
+            self.milvus_ltm_client._client.delete(self.storage_name, expr)
+        else:
+            raise KeyError(f"Key {key} not found")
+
+    def __contains__(self, key: Any) -> bool:
+        key_str = str(key)
+        expr = f'key == "{key_str}"'
+        # Adjust the query call to match the expected signature
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name,  # Pass the collection name as the first argument
+            filter=expr,
+            output_fields=["key"],
+        )
+        return len(res) > 0
+
+    """
+    def __len__(self) -> int:
+        milvus_ltm.collection.flush()
+        return self.collection.num_entities
+    """
+
+    def __len__(self) -> int:
+        expr = 'key != ""'  # Expression to match all entities
+        # self.milvus_ltm_client._client.load(refresh=True)
+        results = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["key"], consistency_level="Strong"
+        )
+        return len(results)
+
+    def keys(self, limit=10) -> Iterable[Any]:
+        expr = ""
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["key"], limit=limit
+        )
+        return (item["key"] for item in res)
+
+    def values(self) -> Iterable[Any]:
+        expr = 'key != ""'  # Expression to match all active entities
+        self.milvus_ltm_client._client.load(refresh=True)
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["value"], consistency_level="Strong"
+        )
+        for item in res:
+            value_base64 = item["value"]
+            value_bytes = base64.b64decode(value_base64)
+            value = pickle.loads(value_bytes)
+            yield value
+
+    def items(self) -> Iterable[Tuple[Any, Any]]:
+        expr = 'key != ""'
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["key", "value"]
+        )
+        for item in res:
+            key = item["key"]
+            value = item["value"]
+            # value_bytes = base64.b64decode(value_base64)
+            # value = pickle.loads(value_bytes)
+            yield (key, value)
+
+    def get(self, key: Any, default: Any = None) -> Any:
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def clear(self) -> None:
+        expr = (
+            'key != ""'  # This expression matches all records where 'key' is not empty
+        )
+        self.milvus_ltm_client._client.delete(self.storage_name, filter=expr)
+
+    def pop(self, key: Any, default: Any = None) -> Any:
+        try:
+            value = self[key]
+            self.__delitem__(key)
+            return value
+        except KeyError:
+            if default is not None:
+                return default
+            else:
+                raise
+
+    def update(self, other: Iterable[Tuple[Any, Any]]) -> None:
+        for key, value in other:
+            self[key] = value
+
+    def get_by_vector(
+        self,
+        embedding: List[float],
+        top_k: int = 10,
+        threshold: float = 0.0,
+        filter: str = "",
+    ) -> List[Tuple[Any, Any, float]]:
+        search_params = {
+            "metric_type": "COSINE",
+            "params": {"nprobe": 10, "range_filter": 1, "radius": threshold},
+        }
+        results = self.milvus_ltm_client._client.search(
+            self.storage_name,
+            data=[embedding],
+            anns_field="embedding",
+            search_params=search_params,
+            limit=top_k,
+            output_fields=["key", "value"],
+            consistency_level="Strong",
+            filter=filter,
+        )
+
+        items = []
+        for match in results[0]:
+            key = match.get("entity").get("key")
+            value = match.get("entity").get("value")
+            items.append((key, value))
+
+        return items
diff --git a/agent/misc/scene.py b/agent/misc/scene.py
new file mode 100755
index 0000000000000000000000000000000000000000..0d690dbf0fe014eca7c866f1204eaeef857390af
--- /dev/null
+++ b/agent/misc/scene.py
@@ -0,0 +1,249 @@
+from typing import Dict, List, Optional, Tuple, Union
+
+from PIL import Image
+from pydantic import BaseModel
+from pydub import AudioSegment
+from pydub.effects import normalize
+from scenedetect import (ContentDetector, FrameTimecode, SceneManager,
+                         VideoStream, open_video)
+
+
+class Scene(BaseModel):
+    start: FrameTimecode
+    end: FrameTimecode
+    stt_res: Optional[Dict] = None
+    summary: Optional[Dict] = None
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        arbitrary_types_allowed = True
+
+    @classmethod
+    def init(cls, start: FrameTimecode, end: FrameTimecode, summary: dict = None):
+        return cls(start=start, end=end, summary=summary)
+
+    @property
+    def conversation(self):
+        # for self deployed whisper
+        if isinstance(self.stt_res, list):
+            output_conversation = "\n".join(
+                [f"{item.get('text', None)}" for item in self.stt_res]
+            )
+        else:
+            output_conversation = self.stt_res
+        return output_conversation
+
+
+class VideoScenes(BaseModel):
+    stream: VideoStream
+    audio: Union[AudioSegment, None]
+    scenes: List[Scene]
+    frame_extraction_interval: int
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    @classmethod
+    def load(
+        cls,
+        video_path: str,
+        threshold: int = 27,
+        min_scene_len: int = 1,
+        frame_extraction_interval: int = 5,
+        show_progress: bool = False,
+        kernel_size: Optional[int] = None,
+    ):
+        """Load a video file.
+
+        Args:
+            video_path (str): The path of the video file. Only support local file.
+            threshold (int): The scene detection threshold.
+            min_scene_len (int): Once a cut is detected, this long time must pass before a new one can
+                be added to the scene list. Count in seconds, defaults to 1.
+            show_progress (bool, optional): Whether to display the progress bar when processing the video. Defaults to False.
+        """
+        video = open_video(video_path)
+        scene_manager = SceneManager()
+        weight = ContentDetector.Components(
+            delta_hue=1.0,
+            delta_sat=1.0,
+            delta_lum=0.0,
+            delta_edges=1.0,
+        )
+        if kernel_size is None:
+            scene_manager.add_detector(
+                ContentDetector(
+                    threshold=threshold,
+                    min_scene_len=int(video.frame_rate * min_scene_len),
+                    weights=weight,
+                )
+            )
+        else:
+            scene_manager.add_detector(
+                ContentDetector(
+                    threshold=threshold,
+                    min_scene_len=int(video.frame_rate * min_scene_len),
+                    weights=weight,
+                    kernel_size=kernel_size,
+                )
+            )
+        scene_manager.detect_scenes(video, show_progress=show_progress)
+        scenes = scene_manager.get_scene_list(start_in_scene=True)
+
+        try:
+            audio = AudioSegment.from_file(video_path)
+            audio = normalize(audio)
+        except (IndexError, OSError):
+            audio = None
+        return cls(
+            stream=video,
+            scenes=[Scene.init(*scene) for scene in scenes],
+            audio=audio,
+            frame_extraction_interval=frame_extraction_interval,
+        )
+
+    def get_video_frames(
+        self, scene: Union[int, Scene, Tuple[FrameTimecode]], interval: int = None
+    ) -> Tuple[List[Image.Image], List[float]]:
+        """Get the frames of a scene.
+
+        Args:
+            scene (Union[int, Scene, Tuple[FrameTimecode]]): The scene to get frames. Can be the index of the scene, the scene object or a tuple of start and end frame timecode.
+            interval (int, optional): The interval of the frames to get. Defaults to None.
+        Raises:
+            ValueError: If the type of scene is not int, Scene or tuple.
+
+        Returns:
+            List[ndarray]: The frames of the scene.
+        """
+        if isinstance(scene, int):
+            scene = self.scenes[scene]
+            start, end = scene.start, scene.end
+        elif isinstance(scene, Scene):
+            start, end = scene.start, scene.end
+        elif isinstance(scene, tuple):
+            start, end = scene
+        else:
+            raise ValueError(
+                f"scene should be int, Scene or tuple, not {type(scene).__name__}"
+            )
+        self.stream.seek(start)
+        frames = []
+        time_stamps = []
+        if interval is None:
+            interval = self.frame_extraction_interval * self.stream.frame_rate
+        scene_len = end.get_frames() - start.get_frames()
+        if scene_len / 10 > interval:
+            interval = int(scene_len / 10) + 1
+        for index in range(scene_len):
+            if index % interval == 0:
+                f = self.stream.read()
+                frames.append(Image.fromarray(f))
+                time_stamps.append(self.stream.position.get_seconds())
+            else:
+                self.stream.read(decode=False)
+        self.stream.seek(0)
+        return frames, time_stamps
+
+    def get_audio_clip(
+        self, scene: Union[int, Scene, Tuple[FrameTimecode]]
+    ) -> AudioSegment:
+        """Get the audio clip of a scene.
+
+        Args:
+            scene (Union[int, Scene, Tuple[FrameTimecode]]): The scene to get audio clip. Can be the index of the scene, the scene object or a tuple of start and end frame timecode.
+
+        Raises:
+            ValueError: If the type of scene is not int, Scene or tuple.
+
+        Returns:
+            AudioSegment: The audio clip of the scene.
+        """
+        if self.audio is None:
+            return None
+        if isinstance(scene, int):
+            scene = self.scenes[scene]
+            start, end = scene.start, scene.end
+        elif isinstance(scene, Scene):
+            start, end = scene.start, scene.end
+        elif isinstance(scene, tuple):
+            start, end = scene
+        else:
+            raise ValueError(
+                f"scene should be int, Scene or tuple, not {type(scene).__name__}"
+            )
+
+        return self.audio[
+            int(start.get_seconds() * 1000) : int(end.get_seconds() * 1000)
+        ]
+
+    def __len__(self):
+        return len(self.scenes)
+
+    def __iter__(self):
+        self.index = 0
+        return self
+
+    def __next__(self):
+        if self.index >= len(self.scenes):
+            raise StopIteration
+        scene = self.scenes[self.index]
+        self.index += 1
+        return scene
+
+    def __getitem__(self, index):
+        return self.scenes[index]
+
+    def __setitem__(self, index, value):
+        self.scenes[index] = value
+
+    def to_serializable(self) -> dict:
+        """Convert VideoScenes to a serializable dictionary."""
+        scenes_data = []
+        for scene in self.scenes:
+            scenes_data.append(
+                {
+                    "start_frame": scene.start.frame_num,
+                    "end_frame": scene.end.frame_num,
+                    "stt_res": scene.stt_res,
+                    "summary": scene.summary,
+                }
+            )
+
+        return {
+            "video_path": self.stream.path,
+            "frame_rate": self.stream.frame_rate,
+            "scenes": scenes_data,
+            "frame_extraction_interval": self.frame_extraction_interval,
+        }
+
+    @classmethod
+    def from_serializable(cls, data: dict):
+        """Rebuild VideoScenes from serialized data."""
+        video = open_video(data["video_path"])
+        try:
+            audio = AudioSegment.from_file(data["video_path"])
+            audio = normalize(audio)
+        except Exception:
+            audio = None
+
+        # Rebuild scenes list
+        scenes = []
+        for scene_data in data["scenes"]:
+            start = FrameTimecode(scene_data["start_frame"], data["frame_rate"])
+            end = FrameTimecode(scene_data["end_frame"], data["frame_rate"])
+            scene = Scene.init(start, end)
+            scene.stt_res = scene_data["stt_res"]
+            scene.summary = scene_data["summary"]
+            scenes.append(scene)
+
+        return cls(
+            stream=video,
+            scenes=scenes,
+            audio=audio,
+            frame_extraction_interval=data["frame_extraction_interval"],
+        )
diff --git a/agent/tools/video_rewinder/rewinder.py b/agent/tools/video_rewinder/rewinder.py
new file mode 100755
index 0000000000000000000000000000000000000000..35963f1a0a75f2017ea3ccc297ab2dfb7a6f5ddb
--- /dev/null
+++ b/agent/tools/video_rewinder/rewinder.py
@@ -0,0 +1,99 @@
+import json
+import re
+from pathlib import Path
+from typing import List
+
+import json_repair
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.tool_system.base import ArgSchema, BaseTool
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from scenedetect import FrameTimecode
+
+from ...misc.scene import VideoScenes
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+ARGSCHEMA = {
+    "start_time": {
+        "type": "number",
+        "description": "Start time (in seconds) of the video to extract frames from.",
+        "required": True,
+    },
+    "end_time": {
+        "type": "number",
+        "description": "End time (in seconds) of the video to extract frames from.",
+        "required": True,
+    },
+    "number": {
+        "type": "number",
+        "description": "Number of frames of extraction. More frames means more details but more cost. Do not exceed 10.",
+        "required": True,
+    },
+}
+
+
+@registry.register_tool()
+class Rewinder(BaseTool, BaseLLMBackend):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = (
+        "Rollback and extract frames from video which is already loaded to get more specific details for further analysis."
+    )
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("rewinder_sys_prompt.prompt"),
+                role="system",
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("rewinder_user_prompt.prompt"),
+                role="user",
+            ),
+        ]
+    )
+
+    def _run(
+        self, start_time: float = 0.0, end_time: float = None, number: int = 1
+    ) -> str:
+        if self.stm(self.workflow_instance_id).get("video", None) is None:
+            raise ValueError("No video is loaded.")
+        else:
+            video: VideoScenes = VideoScenes.from_serializable(
+                self.stm(self.workflow_instance_id)["video"]
+            )
+        if number > 10:
+            logging.warning("Number of frames exceeds 10. Will extract 10 frames.")
+            number = 10
+
+        start = FrameTimecode(timecode=start_time, fps=video.stream.frame_rate)
+        if end_time is None:
+            end = video.stream.duration
+        else:
+            end = FrameTimecode(timecode=end_time, fps=video.stream.frame_rate)
+
+        if start_time == end_time:
+            frames, time_stamps = video.get_video_frames(
+                (start, end + 1), video.stream.frame_rate
+            )
+        else:
+            interval = int((end.get_frames() - start.get_frames()) / number)
+            frames, time_stamps = video.get_video_frames((start, end), interval)
+
+        # self.stm.image_cache.clear()
+        payload = []
+        for i, (frame, time_stamp) in enumerate(zip(frames, time_stamps)):
+            payload.append(f"timestamp_{time_stamp}")
+            payload.append(frame)
+        res = self.infer(input_list=[{"timestamp_with_images": payload}])[0]["choices"][
+            0
+        ]["message"]["content"]
+        image_contents = json_repair.loads(res)
+        self.stm(self.workflow_instance_id)["image_cache"] = {}
+        return f"extracted_frames described as: {image_contents}."
+
+    async def _arun(
+        self, start_time: float = 0.0, end_time: float = None, number: int = 1
+    ) -> str:
+        return self._run(start_time, end_time, number=number)
diff --git a/agent/tools/video_rewinder/rewinder_sys_prompt.prompt b/agent/tools/video_rewinder/rewinder_sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..76f18e1a9856d30b8cdf2d312bdffefe385c72da
--- /dev/null
+++ b/agent/tools/video_rewinder/rewinder_sys_prompt.prompt
@@ -0,0 +1,7 @@
+You are an intelligent agent, your job is to  describe the content of each image and provide a summary for all the images.
+The format should be in JSON data as follows:
+```json{
+  "timestamp_x": "Description of the image at timestamp_x",
+  ...
+  "timestamp_start - timestamp_end": "Summary of all images, where start is the timestamp of the first image and end is the timestamp of the last image"
+}```
diff --git a/agent/tools/video_rewinder/rewinder_user_prompt.prompt b/agent/tools/video_rewinder/rewinder_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..99e9b8a694ddcf6a1684231b03cbd301548fcabb
--- /dev/null
+++ b/agent/tools/video_rewinder/rewinder_user_prompt.prompt
@@ -0,0 +1 @@
+{{timestamp_with_images}}
\ No newline at end of file
diff --git a/agent/video_preprocessor/__init__.py b/agent/video_preprocessor/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/agent/video_preprocessor/sys_prompt.prompt b/agent/video_preprocessor/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..1cf15dfafb6aec87b0b2895c1898b60ef2dfeb02
--- /dev/null
+++ b/agent/video_preprocessor/sys_prompt.prompt
@@ -0,0 +1,18 @@
+You are the most powerful AI agent with the ability to see images. You will help me with video content comprehension and analysis based on continuous video frame extraction and textual content of video conversations.
+
+--- Output ---
+You will be provided with a series of video frame images arranged in the order of video playback. Please help me answer the following questions and provide the output in the specified json format.
+
+{
+    "time": Optional[str].The time information of current video clip in terms of periods like morning or evening, seasons like spring or autumn, or specific years and time points. Please make sure to directly obtain the information from the provided context without inference or fabrication. If the relevant information cannot be obtained, please return null.
+    "location": Optional[str]. Describe the location where the current event is taking place, including scene details. If the relevant information cannot be obtained, please return null.
+    "character": Optional[str]. Provide a detailed description of the current characters, including their names, relationships, and what they are doing, etc. If the relevant information cannot be obtained, please return null.
+    "events": List[str]. List and describe all the detailed events in the video content in chronological order. Please integrate the information provided by the video frames and the textual information in the audio, and do not overlook any key points.
+    "scene": List[str]. Give some detailed description of the scene of the video. This includes, but is not limited to, scene information, textual information, character status expressions, and events displayed in the video.
+    "summary": str. Provide an detailed overall description and summary of the content of this video clip. Ensure that it remains objective and does not include any speculation or fabrication. This field is mandatory.
+}
+
+
+*** Important Notice ***
+1. You will be provided with the video frames and speech-to-text results. You have enough information to answer the questions.
+2. Sometimes the speech-to-text results maybe empty since there are no person talking. Please analyze based on the information in the images in this situation.
\ No newline at end of file
diff --git a/agent/video_preprocessor/user_prompt.prompt b/agent/video_preprocessor/user_prompt.prompt
new file mode 100755
index 0000000000000000000000000000000000000000..ca7849d5b499824693c7556bbc1ab3658e43b61b
--- /dev/null
+++ b/agent/video_preprocessor/user_prompt.prompt
@@ -0,0 +1,4 @@
+Now, it's your turn to complete the task. 
+
+Textual content of video conversations: {{stt_res}}
+Frame images of video playback: {{img_placeholders}}
\ No newline at end of file
diff --git a/agent/video_preprocessor/video_preprocess.py b/agent/video_preprocessor/video_preprocess.py
new file mode 100755
index 0000000000000000000000000000000000000000..14c1a2e07af8244d859398db52f5938fd481a93f
--- /dev/null
+++ b/agent/video_preprocessor/video_preprocess.py
@@ -0,0 +1,254 @@
+import hashlib
+import pickle
+import time
+from pathlib import Path
+from typing import List, Optional, Union
+
+import json_repair
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.asr.stt import STT
+from omagent_core.models.encoders.openai_encoder import OpenaiTextEmbeddingV3
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.registry import registry
+from pydantic import Field, field_validator
+from pydub import AudioSegment
+from pydub.effects import normalize
+from scenedetect import open_video
+
+from ..misc.scene import VideoScenes
+
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class VideoPreprocessor(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    text_encoder: OpenaiTextEmbeddingV3
+
+    stt: STT
+    scene_detect_threshold: Union[float, int] = 27
+    min_scene_len: int = 1
+    frame_extraction_interval: int = 5
+    kernel_size: Optional[int] = None
+    show_progress: bool = True
+
+    use_cache: bool = False
+    cache_dir: str = "./video_cache"
+
+    @field_validator("stt", mode="before")
+    @classmethod
+    def validate_asr(cls, stt):
+        if isinstance(stt, STT):
+            return stt
+        elif isinstance(stt, dict):
+            return STT(**stt)
+        else:
+            raise ValueError("Invalid STT type.")
+
+    def calculate_md5(self, file_path):
+        md5_hash = hashlib.md5()
+        with open(file_path, "rb") as file:
+            for byte_block in iter(lambda: file.read(4096), b""):
+                md5_hash.update(byte_block)
+        return md5_hash.hexdigest()
+
+    def _run(self, test: str, *args, **kwargs):
+        """
+        Process video files by:
+        1. Calculating MD5 hash of input video for caching
+        2. Loading video from cache if available and use_cache=True
+        3. Otherwise, processing video by:
+           - Extracting audio and video streams
+           - Detecting scene boundaries
+           - Extracting frames at specified intervals
+           - Generating scene summaries using LLM
+           - Caching results for future use
+
+        Args:
+            video_path (str): Path to input video file
+            *args: Variable length argument list
+            **kwargs: Arbitrary keyword arguments
+
+        Returns:
+            dict: Dictionary containing video_md5 and video_path
+        """
+        video_path = self.input.read_input(
+            workflow_instance_id=self.workflow_instance_id,
+            input_prompt="Please input the video path:",
+        )["messages"][0]["content"][0]["data"]
+        video_md5 = self.calculate_md5(video_path)
+        kwargs["video_md5"] = video_md5
+
+        cache_path = (
+            Path(self.cache_dir)
+            .joinpath(video_path.replace("/", "-"))
+            .joinpath("video_cache.pkl")
+        )
+        # Load video from cache if available
+        if self.use_cache and cache_path.exists():
+            with open(cache_path, "rb") as f:
+                loaded_scene = pickle.load(f)
+                try:
+                    audio = AudioSegment.from_file(video_path)
+                    audio = normalize(audio)
+                except Exception:
+                    audio = None
+                video = VideoScenes(
+                    stream=open_video(video_path),
+                    audio=audio,
+                    scenes=loaded_scene,
+                    frame_extraction_interval=self.frame_extraction_interval,
+                )
+                self.callback.send_block(
+                    agent_id=self.workflow_instance_id,
+                    msg="Loaded video scenes from cache.\nResume the interrupted transfer for results with scene.summary of None.",
+                )
+                for index, scene in enumerate(video.scenes):
+                    if scene.summary is None:
+                        self.callback.send_block(
+                            agent_id=self.workflow_instance_id,
+                            msg=f"Resume the interrupted transfer for scene {index}.",
+                        )
+                        video_frames, time_stamps = video.get_video_frames(scene)
+                        try:
+                            chat_complete_res = self.infer(
+                                input_list=[
+                                    {
+                                        "stt_res": scene.conversation,
+                                        "img_placeholders": "".join(
+                                            [
+                                                f"<image_{i}>"
+                                                for i in range(len(video_frames))
+                                            ]
+                                        ),
+                                    }
+                                ],
+                                images=video_frames,
+                            )
+                            scene.summary = chat_complete_res[0]["choices"][0][
+                                "message"
+                            ]["content"]
+                            scene_info = scene.summary.get("scene", [])
+                            events = scene.summary.get("events", [])
+                            start_time = scene.start.get_seconds()
+                            end_time = scene.end.get_seconds()
+                            content = (
+                                f"Time in video: {scene.summary.get('time', 'null')}\n"
+                                f"Location: {scene.summary.get('location', 'null')}\n"
+                                f"Character': {scene.summary.get('character', 'null')}\n"
+                                f"Events: {events}\n"
+                                f"Scene: {scene_info}\n"
+                                f"Summary: {scene.summary.get('summary', '')}"
+                            )
+                            content_vector = self.text_encoder.infer([content])[0]
+                            self.ltm[index] = {
+                                "value": {
+                                    "video_md5": video_md5,
+                                    "content": content,
+                                    "start_time": start_time,
+                                    "end_time": end_time,
+                                },
+                                "embedding": content_vector,
+                            }
+                        except Exception as e:
+                            self.callback.error(
+                                f"Failed to resume scene {index}: {e}. Set to default."
+                            )
+                            scene.summary = {
+                                "time": "",
+                                "location": "",
+                                "character": "",
+                                "events": [],
+                                "scene": [],
+                                "summary": "",
+                            }
+                self.stm(self.workflow_instance_id)["video"] = video.to_serializable()
+            # Cache the processed video scenes
+            with open(cache_path, "wb") as f:
+                pickle.dump(video.scenes, f)
+
+        # Process video if not loaded from cache
+        if not self.stm(self.workflow_instance_id).get("video", None):
+            video = VideoScenes.load(
+                video_path=video_path,
+                threshold=self.scene_detect_threshold,
+                min_scene_len=self.min_scene_len,
+                frame_extraction_interval=self.frame_extraction_interval,
+                show_progress=self.show_progress,
+                kernel_size=self.kernel_size,
+            )
+            self.stm(self.workflow_instance_id)["video"] = video.to_serializable()
+
+            for index, scene in enumerate(video.scenes):
+                print(f"Processing scene {index} / {len(video.scenes)}...")
+                audio_clip = video.get_audio_clip(scene)
+                if audio_clip is None:
+                    scene.stt_res = {"text": ""}
+                else:
+                    scene.stt_res = self.stt.infer(audio_clip)
+                video_frames, time_stamps = video.get_video_frames(scene)
+                try:
+                    face_rec = registry.get_tool("FaceRecognition")
+                    for frame in video_frames:
+                        objs = face_rec.infer(frame)
+                        face_rec.visual_prompting(frame, objs)
+                except Exception:
+                    pass
+                try:
+                    chat_complete_res = self.infer(
+                        input_list=[
+                            {
+                                "stt_res": scene.conversation,
+                                "img_placeholders": "".join(
+                                    [f"<image_{i}>" for i in range(len(video_frames))]
+                                ),
+                            }
+                        ],
+                        images=video_frames,
+                    )
+                    scene.summary = chat_complete_res[0]["choices"][0]["message"][
+                        "content"
+                    ]
+                    scene_info = scene.summary.get("scene", [])
+                    events = scene.summary.get("events", [])
+                    start_time = scene.start.get_seconds()
+                    end_time = scene.end.get_seconds()
+                    content = (
+                        f"Time in video: {scene.summary.get('time', 'null')}\n"
+                        f"Location: {scene.summary.get('location', 'null')}\n"
+                        f"Character': {scene.summary.get('character', 'null')}\n"
+                        f"Events: {events}\n"
+                        f"Scene: {scene_info}\n"
+                        f"Summary: {scene.summary.get('summary', '')}"
+                    )
+                    content_vector = self.text_encoder.infer([content])[0]
+                    self.ltm[index] = {
+                        "value": {
+                            "video_md5": video_md5,
+                            "content": content,
+                            "start_time": start_time,
+                            "end_time": end_time,
+                        },
+                        "embedding": content_vector,
+                    }
+                except Exception as e:
+                    self.callback.error(f"Failed to process scene {index}: {e}")
+                    scene.summary = None
+
+        if self.use_cache and not cache_path.exists():
+            cache_path.parent.mkdir(parents=True, exist_ok=True)
+            with open(cache_path, "wb") as f:
+                pickle.dump(video.scenes, f)
+        return {
+            "video_md5": video_md5
+        }
diff --git a/agent/video_preprocessor/webpage_vp.py b/agent/video_preprocessor/webpage_vp.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff70d96d8398fec9139ebf81aceb1497ed94c22c
--- /dev/null
+++ b/agent/video_preprocessor/webpage_vp.py
@@ -0,0 +1,252 @@
+import hashlib
+import pickle
+import time
+from pathlib import Path
+from typing import List, Optional, Union
+
+import json_repair
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.asr.stt import STT
+from omagent_core.models.encoders.openai_encoder import OpenaiTextEmbeddingV3
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.registry import registry
+from pydantic import Field, field_validator
+from pydub import AudioSegment
+from pydub.effects import normalize
+from scenedetect import open_video
+
+from ..misc.scene import VideoScenes
+
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class WebpageVideoPreprocessor(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    text_encoder: OpenaiTextEmbeddingV3
+    
+    stt: STT
+    scene_detect_threshold: Union[float, int] = 27
+    min_scene_len: int = 1
+    frame_extraction_interval: int = 5
+    kernel_size: Optional[int] = None
+    show_progress: bool = True
+    
+    use_cache: bool = False
+    cache_dir: str = "./video_cache"
+    
+    @field_validator("stt", mode="before")
+    @classmethod
+    def validate_asr(cls, stt):
+        if isinstance(stt, STT):
+            return stt
+        elif isinstance(stt, dict):
+            return STT(**stt)
+        else:
+            raise ValueError("Invalid STT type.")
+    
+    def calculate_md5(self, file_path):
+        md5_hash = hashlib.md5()
+        with open(file_path, "rb") as file:
+            for byte_block in iter(lambda: file.read(4096), b""):
+                md5_hash.update(byte_block)
+        return md5_hash.hexdigest()
+    
+    def _run(self, video_path: str, *args, **kwargs):
+        """
+        Process video files by:
+        1. Calculating MD5 hash of input video for caching
+        2. Loading video from cache if available and use_cache=True
+        3. Otherwise, processing video by:
+           - Extracting audio and video streams
+           - Detecting scene boundaries
+           - Extracting frames at specified intervals
+           - Generating scene summaries using LLM
+           - Caching results for future use
+
+        Args:
+            video_path (str): Path to input video file
+            *args: Variable length argument list
+            **kwargs: Arbitrary keyword arguments
+
+        Returns:
+            dict: Dictionary containing video_md5 and video_path
+        """
+        video_md5 = self.calculate_md5(video_path)
+        kwargs["video_md5"] = video_md5
+        
+        cache_path = (
+            Path(self.cache_dir)
+            .joinpath(video_path.replace("/", "-"))
+            .joinpath("video_cache.pkl")
+        )
+        # Load video from cache if available
+        if self.use_cache and cache_path.exists():
+            with open(cache_path, "rb") as f:
+                loaded_scene = pickle.load(f)
+                try:
+                    audio = AudioSegment.from_file(video_path)
+                    audio = normalize(audio)
+                except Exception:
+                    audio = None
+                video = VideoScenes(
+                    stream=open_video(video_path),
+                    audio=audio,
+                    scenes=loaded_scene,
+                    frame_extraction_interval=self.frame_extraction_interval,
+                )
+                self.callback.send_block(
+                    agent_id=self.workflow_instance_id,
+                    msg="Loaded video scenes from cache.\nResume the interrupted transfer for results with scene.summary of None.",
+                )
+                for index, scene in enumerate(video.scenes):
+                    if scene.summary is None:
+                        self.callback.send_block(
+                            agent_id=self.workflow_instance_id,
+                            msg=f"Resume the interrupted transfer for scene {index}.",
+                        )
+                        video_frames, time_stamps = video.get_video_frames(scene)
+                        try:
+                            chat_complete_res = self.infer(
+                                input_list=[
+                                    {
+                                        "stt_res": scene.conversation,
+                                        "img_placeholders": "".join(
+                                            [
+                                                f"<image_{i}>"
+                                                for i in range(len(video_frames))
+                                            ]
+                                        ),
+                                    }
+                                ],
+                                images=video_frames,
+                            )
+                            scene.summary = chat_complete_res[0]["choices"][0][
+                                "message"
+                            ]["content"]
+                            scene_info = scene.summary.get("scene", [])
+                            events = scene.summary.get("events", [])
+                            start_time = scene.start.get_seconds()
+                            end_time = scene.end.get_seconds()
+                            content = (
+                                f"Time in video: {scene.summary.get('time', 'null')}\n"
+                                f"Location: {scene.summary.get('location', 'null')}\n"
+                                f"Character': {scene.summary.get('character', 'null')}\n"
+                                f"Events: {events}\n"
+                                f"Scene: {scene_info}\n"
+                                f"Summary: {scene.summary.get('summary', '')}"
+                            )
+                            content_vector = self.text_encoder.infer([content])[0]
+                            self.ltm[index] = {
+                                "value": {
+                                    "video_md5": video_md5,
+                                    "content": content,
+                                    "start_time": start_time,
+                                    "end_time": end_time,
+                                },
+                                "embedding": content_vector,
+                            }
+                        except Exception as e:
+                            self.callback.error(
+                                f"Failed to resume scene {index}: {e}. Set to default."
+                            )
+                            scene.summary = {
+                                "time": "",
+                                "location": "",
+                                "character": "",
+                                "events": [],
+                                "scene": [],
+                                "summary": "",
+                            }
+                self.stm(self.workflow_instance_id)["video"] = video.to_serializable()
+            # Cache the processed video scenes
+            with open(cache_path, "wb") as f:
+                pickle.dump(video.scenes, f)
+        
+        # Process video if not loaded from cache
+        if not self.stm(self.workflow_instance_id).get("video", None):
+            video = VideoScenes.load(
+                video_path=video_path,
+                threshold=self.scene_detect_threshold,
+                min_scene_len=self.min_scene_len,
+                frame_extraction_interval=self.frame_extraction_interval,
+                show_progress=self.show_progress,
+                kernel_size=self.kernel_size,
+            )
+            self.stm(self.workflow_instance_id)["video"] = video.to_serializable()
+            
+            for index, scene in enumerate(video.scenes):
+                print(f"Processing scene {index} / {len(video.scenes)}...")
+                audio_clip = video.get_audio_clip(scene)
+                if audio_clip is None:
+                    scene.stt_res = {"text": ""}
+                else:
+                    scene.stt_res = self.stt.infer(audio_clip)
+                video_frames, time_stamps = video.get_video_frames(scene)
+                try:
+                    face_rec = registry.get_tool("FaceRecognition")
+                    for frame in video_frames:
+                        objs = face_rec.infer(frame)
+                        face_rec.visual_prompting(frame, objs)
+                except Exception:
+                    pass
+                try:
+                    chat_complete_res = self.infer(
+                        input_list=[
+                            {
+                                "stt_res": scene.conversation,
+                                "img_placeholders": "".join(
+                                    [f"<image_{i}>" for i in range(len(video_frames))]
+                                ),
+                            }
+                        ],
+                        images=video_frames,
+                    )
+                    scene.summary = chat_complete_res[0]["choices"][0]["message"][
+                        "content"
+                    ]
+                    scene_info = scene.summary.get("scene", [])
+                    events = scene.summary.get("events", [])
+                    start_time = scene.start.get_seconds()
+                    end_time = scene.end.get_seconds()
+                    content = (
+                        f"Time in video: {scene.summary.get('time', 'null')}\n"
+                        f"Location: {scene.summary.get('location', 'null')}\n"
+                        f"Character': {scene.summary.get('character', 'null')}\n"
+                        f"Events: {events}\n"
+                        f"Scene: {scene_info}\n"
+                        f"Summary: {scene.summary.get('summary', '')}"
+                    )
+                    content_vector = self.text_encoder.infer([content])[0]
+                    self.ltm[index] = {
+                        "value": {
+                            "video_md5": video_md5,
+                            "content": content,
+                            "start_time": start_time,
+                            "end_time": end_time,
+                        },
+                        "embedding": content_vector,
+                    }
+                except Exception as e:
+                    self.callback.error(f"Failed to process scene {index}: {e}")
+                    scene.summary = None
+        
+        if self.use_cache and not cache_path.exists():
+            cache_path.parent.mkdir(parents=True, exist_ok=True)
+            with open(cache_path, "wb") as f:
+                pickle.dump(video.scenes, f)
+        return {
+            "video_md5": video_md5,
+            "video_path": video_path,
+            "instance_id": self.workflow_instance_id,
+        }
diff --git a/agent/video_qa/__init__.py b/agent/video_qa/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/agent/video_qa/qa.py b/agent/video_qa/qa.py
new file mode 100755
index 0000000000000000000000000000000000000000..e13468753e3fdeae7db35410dab6f4c9fcf9f395
--- /dev/null
+++ b/agent/video_qa/qa.py
@@ -0,0 +1,82 @@
+import json
+import re
+from pathlib import Path
+from typing import List
+
+import json_repair
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import \
+    TaskTree
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.memories.ltms.ltm import LTM
+from omagent_core.models.encoders.openai_encoder import OpenaiTextEmbeddingV3
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.registry import registry
+from pydantic import Field
+
+from ..misc.scene import VideoScenes
+
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class VideoQA(BaseWorker, BaseLLMBackend):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    text_encoder: OpenaiTextEmbeddingV3
+
+    def _run(self, video_md5: str, *args, **kwargs):
+        self.stm(self.workflow_instance_id)["image_cache"] = {}
+        self.stm(self.workflow_instance_id)["former_results"] = {}
+        question = self.input.read_input(
+            workflow_instance_id=self.workflow_instance_id,
+            input_prompt="Please input your question:",
+        )["messages"][0]["content"][0]["data"]
+        chat_complete_res = self.simple_infer(question=question)
+        content = chat_complete_res["choices"][0]["message"]["content"]
+        content = json_repair.loads(content)
+        try:
+            start_time = (
+                None if content.get("start_time", -1) == -1 else content.get("start_time")
+            )
+            end_time = (
+                None if content.get("end_time", -1) == -1 else content.get("end_time")
+            )
+        except Exception as e:
+            start_time = None
+            end_time = None
+        question_vector = self.text_encoder.infer([question])[0]
+        filter_expr = ""
+        if video_md5 is not None:
+            filter_expr = f"value['video_md5']=='{video_md5}'"
+        if start_time is not None and end_time is not None:
+            filter_expr += f" and (value['start_time']>={max(0, start_time - 10)} and value['end_time']<={end_time + 10})"
+        elif start_time is not None:
+            filter_expr += f" and value['start_time']>={max(0, start_time - 10)}"
+        elif end_time is not None:
+            filter_expr += f" and value['end_time']<={end_time + 10}"
+        related_information = self.ltm.get_by_vector(
+            embedding=question_vector, top_k=5, threshold=0.2, filter=filter_expr
+        )
+        related_information = [
+            f"Time span: {each['start_time']} - {each['end_time']}\n{each['content']}"
+            for _, each in related_information
+        ]
+        video = VideoScenes.from_serializable(
+            self.stm(self.workflow_instance_id)["video"]
+        )
+        self.stm(self.workflow_instance_id)["extra"] = {
+            "video_information": "video is already loaded in the short-term memory(stm).",
+            "video_duration_seconds(s)": video.stream.duration.get_seconds(),
+            "frame_rate": video.stream.frame_rate,
+            "video_summary": "\n---\n".join(related_information),
+        }
+        return {"query": question, "last_output": None}
diff --git a/agent/video_qa/sys_prompt.prompt b/agent/video_qa/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e2f3b89dde9abd5ba0618f866fd3ca885ec76d15
--- /dev/null
+++ b/agent/video_qa/sys_prompt.prompt
@@ -0,0 +1,8 @@
+You are a master of time extraction, capable of analyzing the temporal relationships in question and extracting timestamps, with the extracted times in seconds.
+Time format in question is in the form of "HH:MM:SS" or "MM:SS".
+---
+The output should be a json object as follows:
+{
+    "start_time": start time in seconds, -1 if not found,
+    "end_time": end time in seconds, -1 if not found,
+}
\ No newline at end of file
diff --git a/agent/video_qa/user_prompt.prompt b/agent/video_qa/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..93102bf3a2a5d2bffbd129cd7163b82167b75d6b
--- /dev/null
+++ b/agent/video_qa/user_prompt.prompt
@@ -0,0 +1 @@
+Question: {{question}}
diff --git a/agent/video_qa/webpage_qa.py b/agent/video_qa/webpage_qa.py
new file mode 100644
index 0000000000000000000000000000000000000000..f9492160c310e05d75eef7fd67fd75236e947771
--- /dev/null
+++ b/agent/video_qa/webpage_qa.py
@@ -0,0 +1,73 @@
+from pathlib import Path
+from typing import List
+
+import json_repair
+from pydantic import Field
+
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.encoders.openai_encoder import OpenaiTextEmbeddingV3
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.registry import registry
+from ..misc.scene import VideoScenes
+
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class WebpageVideoQA(BaseWorker, BaseLLMBackend):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    text_encoder: OpenaiTextEmbeddingV3
+    
+    def _run(self, video_md5: str, video_path: str, instance_id: str, question: str, *args, **kwargs):
+        self.stm(self.workflow_instance_id)["image_cache"] = {}
+        self.stm(self.workflow_instance_id)["former_results"] = {}
+        chat_complete_res = self.simple_infer(question=question)
+        content = chat_complete_res["choices"][0]["message"]["content"]
+        content = json_repair.loads(content)
+        try:
+            start_time = (
+                None if content.get("start_time", -1) == -1 else content.get("start_time")
+            )
+            end_time = (
+                None if content.get("end_time", -1) == -1 else content.get("end_time")
+            )
+        except Exception as e:
+            start_time = None
+            end_time = None
+        question_vector = self.text_encoder.infer([question])[0]
+        filter_expr = ""
+        if video_md5 is not None:
+            filter_expr = f"value['video_md5']=='{video_md5}'"
+        if start_time is not None and end_time is not None:
+            filter_expr += f" and (value['start_time']>={max(0, start_time - 10)} and value['end_time']<={end_time + 10})"
+        elif start_time is not None:
+            filter_expr += f" and value['start_time']>={max(0, start_time - 10)}"
+        elif end_time is not None:
+            filter_expr += f" and value['end_time']<={end_time + 10}"
+        related_information = self.ltm.get_by_vector(
+            embedding=question_vector, top_k=5, threshold=0.2, filter=filter_expr
+        )
+        related_information = [
+            f"Time span: {each['start_time']} - {each['end_time']}\n{each['content']}"
+            for _, each in related_information
+        ]
+        video = VideoScenes.from_serializable(
+            self.stm(self.workflow_instance_id)["video"]
+        )
+        self.stm(self.workflow_instance_id)["extra"] = {
+            "video_information": "video is already loaded in the short-term memory(stm).",
+            "video_duration_seconds(s)": video.stream.duration.get_seconds(),
+            "frame_rate": video.stream.frame_rate,
+            "video_summary": "\n---\n".join(related_information),
+        }
+        return {"query": question, "last_output": None}
diff --git a/app.py b/app.py
new file mode 100644
index 0000000000000000000000000000000000000000..9be482c9452417589103437a8a6e1227d062a781
--- /dev/null
+++ b/app.py
@@ -0,0 +1,136 @@
+# Import required modules and components
+import base64
+import hashlib
+import json
+import os
+from pathlib import Path
+
+from Crypto.Cipher import AES
+
+
+class Encrypt(object):
+    
+    @staticmethod
+    def pad(s):
+        AES_BLOCK_SIZE = 16  # Bytes
+        return s + (AES_BLOCK_SIZE - len(s) % AES_BLOCK_SIZE) * \
+            chr(AES_BLOCK_SIZE - len(s) % AES_BLOCK_SIZE)
+    
+    @staticmethod
+    def unpad(s):
+        return s[:-ord(s[len(s) - 1:])]
+    
+    # hashlib md5加密
+    @staticmethod
+    def hash_md5_encrypt(data: (str, bytes), salt=None) -> str:
+        if isinstance(data, str):
+            data = data.encode('utf-8')
+        md5 = hashlib.md5()
+        if salt:
+            if isinstance(salt, str):
+                salt = salt.encode('utf-8')
+            md5.update(salt)
+        md5.update(data)
+        return md5.hexdigest()
+    
+    @staticmethod
+    # @catch_exc()
+    def aes_decrypt(key: str, data: str) -> str:
+        '''
+        :param key: 密钥
+        :param data: 加密后的数据(密文)
+        :return:明文
+        '''
+        key = key.encode('utf8')
+        data = base64.b64decode(data)
+        cipher = AES.new(key, AES.MODE_ECB)
+        # 去补位
+        text_decrypted = Encrypt.unpad(cipher.decrypt(data))
+        text_decrypted = text_decrypted.decode('utf8')
+        return text_decrypted
+
+
+secret = 'FwALd7BY8IUrbnrigH3YYlhGD/XvMVX7'
+encrypt = 'sJWveD1LIxIxYGZvZMRlb+8vJjq5yJmXnqSKfHM6AhgmZWMPcFuTNbpJCHNVnjqminXZLsIbFWazoyAUNP1piKOrtBGHF8NaunP/6lp2CJKVMIrxo8z/IxN0IstwcULjaFLilf68/PFXhwZ1gv4PZmu2Z2iwSLAyVxXkmIwjFUp0TQv7xtHpwj2KH/80BgjAOGFlZ8OSwlsum9BqD68a1q3QMi1IcyG1SlUSiiKB5bREfhfXxCgOV2EOYrPPurrT/hHLuZaFSu2YjV/ZkEkumjJZu5sGUElw7dZWdNhJibMjtsA4saNBrjp6gO3/q4i1YLWKTM5HQeTjMkAHt0FH2FigXIER1xZqma94bIaDZoo='
+
+env = json.loads(Encrypt.aes_decrypt(secret, encrypt))
+for k, v in env.items():
+    os.environ.setdefault(k, v)
+from agent.conclude.webpage_conclude import WebpageConclude
+from agent.video_preprocessor.webpage_vp import WebpageVideoPreprocessor
+from agent.video_qa.webpage_qa import WebpageVideoQA
+from webpage import WebpageClient
+
+from omagent_core.advanced_components.workflow.dnc.workflow import DnCWorkflow
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+
+def app():
+    logging.init_logger("omagent", "omagent", level="INFO")
+    
+    # Set current working directory path
+    CURRENT_PATH = root_path = Path(__file__).parents[0]
+    
+    # Import registered modules
+    registry.import_module(project_path=CURRENT_PATH.joinpath("agent"))
+    
+    # Load container configuration from YAML file
+    container.register_stm("SharedMemSTM")
+    container.register_ltm(ltm="VideoMilvusLTM")
+    container.from_config(CURRENT_PATH.joinpath("container.yaml"))
+    
+    # Initialize simple VQA workflow
+    workflow = ConductorWorkflow(name="webpage_video_understanding")
+    process_workflow = ConductorWorkflow(name="webpage_process_video_understanding")
+    # 1. Video preprocess task for video preprocessing
+    video_preprocess_task = simple_task(
+        task_def_name=WebpageVideoPreprocessor,
+        task_reference_name="webpage_video_preprocess",
+        inputs={"video_path": process_workflow.input("video_path")}
+    )
+    
+    # 2. Video QA task for video QA
+    video_qa_task = simple_task(
+        task_def_name=WebpageVideoQA,
+        task_reference_name="webpage_video_qa",
+        inputs={
+            "video_md5": workflow.input("video_md5"),
+            "video_path": workflow.input("video_path"),
+            "instance_id": workflow.input("instance_id"),
+            "question": workflow.input("question"),
+        },
+    )
+    
+    dnc_workflow = DnCWorkflow()
+    dnc_workflow.set_input(query=video_qa_task.output("query"))
+    # 7. Conclude task for task conclusion
+    conclude_task = simple_task(
+        task_def_name=WebpageConclude,
+        task_reference_name="webpage_task_conclude",
+        inputs={
+            "dnc_structure": dnc_workflow.dnc_structure,
+            "last_output": dnc_workflow.last_output,
+        },
+    )
+    
+    # Configure workflow execution flow: Input -> Initialize global variables -> DnC Loop -> Conclude
+    process_workflow >> video_preprocess_task
+    workflow >> video_preprocess_task >> video_qa_task >> dnc_workflow >> conclude_task
+    
+    # Register workflow
+    workflow.register(overwrite=True)
+    process_workflow.register(overwrite=True)
+    
+    # Initialize and start app client with workflow configuration
+    cli_client = WebpageClient(
+        interactor=workflow, processor=process_workflow, config_path="webpage_configs"
+    )
+    cli_client.start_interactor()
+
+
+if __name__ == '__main__':
+    app()
diff --git a/calculator_code.py b/calculator_code.py
new file mode 100644
index 0000000000000000000000000000000000000000..41ba014ae28d95f8b309739550cad40269e4d589
--- /dev/null
+++ b/calculator_code.py
@@ -0,0 +1,4 @@
+duration = 10.117
+frame_rate = 9.88
+number_of_frames = duration * frame_rate
+print(number_of_frames)
\ No newline at end of file
diff --git a/compile_container.py b/compile_container.py
new file mode 100644
index 0000000000000000000000000000000000000000..0d91278de7a35100e586fa3b52880f3110d01938
--- /dev/null
+++ b/compile_container.py
@@ -0,0 +1,18 @@
+# Import core modules and components
+# Import workflow related modules
+from pathlib import Path
+
+from omagent_core.utils.container import container
+from omagent_core.utils.registry import registry
+
+# Set up path and import modules
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+registry.import_module(project_path=CURRENT_PATH.joinpath("agent"))
+
+# Register required components
+container.register_callback(callback="DefaultCallback")
+container.register_input(input="AppInput")
+container.register_stm("SharedMemSTM")
+container.register_ltm(ltm="VideoMilvusLTM")
+# Compile container config
+container.compile_config(CURRENT_PATH)
diff --git a/configs/llms/gpt4o.yml b/configs/llms/gpt4o.yml
new file mode 100755
index 0000000000000000000000000000000000000000..0d612dfcf04a5b9b4d7bba94b01b495d71df413e
--- /dev/null
+++ b/configs/llms/gpt4o.yml
@@ -0,0 +1,7 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+vision: true
+response_format: json_object
\ No newline at end of file
diff --git a/configs/llms/json_res.yml b/configs/llms/json_res.yml
new file mode 100755
index 0000000000000000000000000000000000000000..c5cf8adbfcd184c269548df686c269394e29ec70
--- /dev/null
+++ b/configs/llms/json_res.yml
@@ -0,0 +1,6 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+response_format: json_object
\ No newline at end of file
diff --git a/configs/llms/text_encoder.yml b/configs/llms/text_encoder.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4df3f875fd1fe27659f4da6633c9b78025514049
--- /dev/null
+++ b/configs/llms/text_encoder.yml
@@ -0,0 +1,3 @@
+name: OpenaiTextEmbeddingV3
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+api_key: ${env| custom_openai_key, openai_api_key}
\ No newline at end of file
diff --git a/configs/llms/text_res.yml b/configs/llms/text_res.yml
new file mode 100755
index 0000000000000000000000000000000000000000..b6c80bdfa8e89d7c14d6866aed7614c18459b64d
--- /dev/null
+++ b/configs/llms/text_res.yml
@@ -0,0 +1,6 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+response_format: text
\ No newline at end of file
diff --git a/configs/llms/text_res_stream.yml b/configs/llms/text_res_stream.yml
new file mode 100755
index 0000000000000000000000000000000000000000..e7dedc25b92798eddf3d42cd9aa7ed37d39addbb
--- /dev/null
+++ b/configs/llms/text_res_stream.yml
@@ -0,0 +1,7 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o-mini
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+stream: true
+response_format: text
\ No newline at end of file
diff --git a/configs/tools/all_tools.yml b/configs/tools/all_tools.yml
new file mode 100755
index 0000000000000000000000000000000000000000..801297af0dc60cd9c97ab498399eb8d18e5e2f0a
--- /dev/null
+++ b/configs/tools/all_tools.yml
@@ -0,0 +1,12 @@
+llm: ${sub|text_res}
+tools:
+    - Calculator
+    - CodeInterpreter
+    - ReadFileContent
+    - WriteFileContent
+    - ShellTool
+    - name: Rewinder
+      llm: ${sub|text_res}
+    - name: WebSearch
+      bing_api_key: ${env|bing_api_key, microsoft_bing_api_key}
+      llm: ${sub|text_res}
diff --git a/configs/workers/conclude.yml b/configs/workers/conclude.yml
new file mode 100755
index 0000000000000000000000000000000000000000..ce218647c775002a30dfdbd283b2ffd0559cb651
--- /dev/null
+++ b/configs/workers/conclude.yml
@@ -0,0 +1,4 @@
+name: Conclude
+llm: ${sub|text_res}
+output_parser: 
+  name: StrParser
\ No newline at end of file
diff --git a/configs/workers/dnc_workflow.yml b/configs/workers/dnc_workflow.yml
new file mode 100755
index 0000000000000000000000000000000000000000..9d248e4037ab95a1f8a7c23bf6bb155649f34906
--- /dev/null
+++ b/configs/workers/dnc_workflow.yml
@@ -0,0 +1,18 @@
+- name: ConstructDncPayload
+- name: StructureUpdate
+- name: TaskConqueror
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskDivider
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskRescue
+  llm: ${sub|text_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskExitMonitor
diff --git a/configs/workers/video_preprocessor.yml b/configs/workers/video_preprocessor.yml
new file mode 100755
index 0000000000000000000000000000000000000000..0fc9083ada1c5acc6253ba37d4bd8cc92a195622
--- /dev/null
+++ b/configs/workers/video_preprocessor.yml
@@ -0,0 +1,12 @@
+name: VideoPreprocessor
+llm: ${sub|gpt4o}
+use_cache: true
+scene_detect_threshold: 27
+frame_extraction_interval: 5
+stt:
+  name: STT
+  endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+  api_key: ${env| custom_openai_key, openai_api_key}
+output_parser: 
+  name: DictParser
+text_encoder: ${sub| text_encoder}
\ No newline at end of file
diff --git a/configs/workers/video_qa.yml b/configs/workers/video_qa.yml
new file mode 100755
index 0000000000000000000000000000000000000000..e8993c41561b792f708dfed965444d099fb3707b
--- /dev/null
+++ b/configs/workers/video_qa.yml
@@ -0,0 +1,5 @@
+name: VideoQA
+llm: ${sub|gpt4o}
+output_parser: 
+  name: StrParser
+text_encoder: ${sub| text_encoder}
\ No newline at end of file
diff --git a/container.yaml b/container.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..0e8605f330ceaa53c6e2273ddc4f3dd9a1823076
--- /dev/null
+++ b/container.yaml
@@ -0,0 +1,154 @@
+conductor_config:
+  name: Configuration
+  base_url:
+    value: http://localhost:8080
+    description: The Conductor Server API endpoint
+    env_var: CONDUCTOR_SERVER_URL
+  auth_key:
+    value: null
+    description: The authorization key
+    env_var: AUTH_KEY
+  auth_secret:
+    value: null
+    description: The authorization secret
+    env_var: CONDUCTOR_AUTH_SECRET
+  auth_token_ttl_min:
+    value: 45
+    description: The authorization token refresh interval in minutes.
+    env_var: AUTH_TOKEN_TTL_MIN
+  debug:
+    value: false
+    description: Debug mode
+    env_var: DEBUG
+aaas_config:
+  name: AaasConfig
+  base_url:
+    value: http://localhost:30002
+    description: The aaas task server API endpoint
+    env_var: AAAS_TASK_SERVER_URL
+  token:
+    value: null
+    description: The authorization token
+    env_var: AAAS_TOKEN
+  enable:
+    value: true
+    description: Whether to enable the aaas task server
+    env_var: AAAS_ENABLE
+  domain_token:
+    value: null
+    description: The domain token
+    env_var: DOMAIN_TOKEN
+  is_prod:
+    value: false
+    description: Whether it is a production environment
+    env_var: IS_PROD
+connectors:
+  redis_stream_client:
+    name: RedisConnector
+    id:
+      value: null
+      env_var: ID
+    host:
+      value: localhost
+      env_var: HOST
+    port:
+      value: 6379
+      env_var: PORT
+    password:
+      value: null
+      env_var: PASSWORD
+    username:
+      value: null
+      env_var: USERNAME
+    db:
+      value: 0
+      env_var: DB
+    use_lite:
+      value: true
+      env_var: USE_LITE
+  milvus_ltm_client:
+    name: MilvusConnector
+    id:
+      value: null
+      env_var: ID
+    host:
+      value: ./db.db
+      env_var: HOST
+    port:
+      value: 19530
+      env_var: PORT
+    password:
+      value: ''
+      env_var: PASSWORD
+    username:
+      value: default
+      env_var: USERNAME
+    db:
+      value: default
+      env_var: DB
+    alias:
+      value: alias
+      env_var: ALIAS
+components:
+  AppCallback:
+    name: AppCallback
+    id:
+      value: null
+      env_var: ID
+    bot_id:
+      value: ''
+      env_var: BOT_ID
+    start_time:
+      value: 2025-02-17_15:40:53
+      env_var: START_TIME
+  AppInput:
+    name: AppInput
+    id:
+      value: null
+      env_var: ID
+  AaasCallback:
+    name: AaasCallback
+    id:
+      value: null
+      env_var: ID
+    bot_id:
+      value: ''
+      env_var: BOT_ID
+    start_time:
+      value: 2025-02-17_15:40:53
+      env_var: START_TIME
+  AaasInput:
+    name: AaasInput
+    id:
+      value: null
+      env_var: ID
+  DefaultCallback:
+    name: DefaultCallback
+    id:
+      value: null
+      env_var: ID
+    bot_id:
+      value: ''
+      env_var: BOT_ID
+    start_time:
+      value: 2025-02-17_15:40:53
+      env_var: START_TIME
+    incomplete_flag:
+      value: false
+      env_var: INCOMPLETE_FLAG
+  SharedMemSTM:
+    name: SharedMemSTM
+    id:
+      value: null
+      env_var: ID
+  VideoMilvusLTM:
+    name: VideoMilvusLTM
+    id:
+      value: null
+      env_var: ID
+    storage_name:
+      value: default
+      env_var: STORAGE_NAME
+    dim:
+      value: 3072
+      env_var: DIM
diff --git a/docs/images/local-ai.png b/docs/images/local-ai.png
new file mode 100644
index 0000000000000000000000000000000000000000..dcb3f652fff195a5cebb22beecf3ba616e89cf3d
Binary files /dev/null and b/docs/images/local-ai.png differ
diff --git a/docs/images/video_understanding_workflow_diagram.png b/docs/images/video_understanding_workflow_diagram.png
new file mode 100644
index 0000000000000000000000000000000000000000..ffcdba7a454e4519d64e222c362c826d62ef6ebd
Binary files /dev/null and b/docs/images/video_understanding_workflow_diagram.png differ
diff --git a/docs/local-ai.md b/docs/local-ai.md
new file mode 100644
index 0000000000000000000000000000000000000000..97ca950021dcba7de8a9c4f4e21fd2d22d9c6dae
--- /dev/null
+++ b/docs/local-ai.md
@@ -0,0 +1,87 @@
+# Local-ai
+You can use Local-ai to run your own model locally.
+Following the instruction of [Local-ai](https://github.com/mudler/LocalAI) to install Local-ai.
+
+### Download Local-ai models
+Download [Whisper](https://huggingface.co/ggerganov/whisper.cpp) and [Embedding model](https://huggingface.co/hugging-quants/Llama-3.2-1B-Instruct-Q4_K_M-GGUF). 
+Then move the model checkpoint file to the /usr/share/local-ai/models/. **Other path for models is not supported.**
+
+### Modify config files
+Create Local-ai config files.
+
+Embedding model yaml
+```yaml
+name: text-embedding-ada-002
+backend: llama-cpp
+embeddings: true
+parameters:
+  model: llama-3.2-1b-instruct-q4_k_m.gguf # model file name in /usr/share/local-ai/models/
+```
+Whisper yaml
+```yaml
+name: whisper
+backend: whisper
+parameters:
+  model: ggml-model-whisper-base.en.bin # model file name in /usr/share/local-ai/models/
+```
+### run the model
+First run
+```bash
+local-ai run <path-to-your-embedding-model-yaml>
+```
+and
+```bash
+local-ai run <path-to-your-whisper-yaml>
+```
+to initially link yaml file to the model.
+
+Then next time only run
+```bash
+local-ai run
+```
+can load two models.
+
+**Make sure get model names right, or embedding model may get empty result.**
+![local-ai get model names right](images/local-ai.png)
+
+### Modify the yaml of OmAgent
+Modify ./configs/llms/json_res.yml
+```yaml
+name: OpenaiTextEmbeddingV3
+model_id: text-embedding-ada-002
+dim: 2048
+endpoint: ${env| custom_openai_endpoint, http://localhost:8080/v1}
+api_key: ${env| custom_openai_key, openai_api_key} # api_key is not needed
+```
+and ./configs/workers/video_preprocessor.yml
+```yaml
+name: VideoPreprocessor
+llm: ${sub|gpt4o}
+use_cache: true
+scene_detect_threshold: 27
+frame_extraction_interval: 5
+stt:
+  name: STT
+  endpoint: http://localhost:8080/v1
+  api_key: ${env| custom_openai_key, openai_api_key}
+  model_id: whisper
+output_parser: 
+  name: DictParser
+text_encoder: ${sub| text_encoder}
+```
+and set dim in ./container.yaml
+```yaml
+  VideoMilvusLTM:
+    name: VideoMilvusLTM
+    id:
+      value: null
+      env_var: ID
+    storage_name:
+      value: yyl_video_ltm
+      env_var: STORAGE_NAME
+    dim:
+      value: 2048
+      env_var: DIM
+```
+
+Then you can use your model locally.
diff --git a/omagent_core/__init__.py b/omagent_core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/__init__.py b/omagent_core/advanced_components/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/worker/__init__.py b/omagent_core/advanced_components/worker/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/worker/conclude/__init__.py b/omagent_core/advanced_components/worker/conclude/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/worker/conqueror/__init__.py b/omagent_core/advanced_components/worker/conqueror/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/worker/divider/__init__.py b/omagent_core/advanced_components/worker/divider/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/worker/task_exit_monitor/__init__.py b/omagent_core/advanced_components/worker/task_exit_monitor/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/worker/video_preprocess/__init__.py b/omagent_core/advanced_components/worker/video_preprocess/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/cot/README.md b/omagent_core/advanced_components/workflow/cot/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..99192a0b4ad757fa8926cac897cf3dca54eaa9c3
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/cot/README.md
@@ -0,0 +1,36 @@
+# Chain-of-Thought Operator
+Chain of Thought (CoT) is a workflow operator that breaks down a complex problem into a series of intermediate steps or thoughts that lead to a final answer.
+
+You can refer to the example in the `examples/cot` directory to understand how to use this operator.
+
+# Inputs, Outputs and configs
+
+## Inputs:
+The inputs that the Chain of Thought (CoT) operator requires are as follows:
+| Name        | Type | Required | Description |
+| ----------- | ----- | -------- | ----------- |
+| query       | str   | true     | The user's question |
+| cot_method  | str   | optional | The CoT method: `few_shot` or `zero_shot` |
+| cot_examples| list  | optional | Examples used for the `few_shot` CoT method |
+| id          | int   | optional | An identifier for tracking the question |
+
+## Outputs:
+The outputs that the Chain of Thought (CoT) operator returns are as follows:
+| Name     | Type | Description |
+| -------- | ----- | ---- |
+| id | int |  An identifier for tracking the question |
+| question | str |  The complete prompt string containing the query |
+| last_output | str | The final answer generated by the agent |
+| prompt_tokens | int | The total number of tokens in the question |
+| completion_tokens | int | The total number of tokens in the answer |
+
+## Configs:
+The config of the Chain of Thought (CoT) operator is as follows, you can simply copy and paste the following config into your project as a cot_workflow.yml file.
+```yml
+- name: CoTReasoning
+  llm: ${sub|gpt4o}
+  output_parser: 
+    name: StrParser
+  concurrency: 15
+
+```
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/cot_reasoning.py b/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/cot_reasoning.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ed305823793c01defcb879740a9cfaa69704cd3
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/cot_reasoning.py
@@ -0,0 +1,67 @@
+from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.schemas import Message, Content
+from omagent_core.utils.logger import logging
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.advanced_components.workflow.cot.schemas.cot_create_examples import CoTExample
+from pydantic import Field
+from pathlib import Path
+from typing import List
+
+CURRENT_PATH = Path( __file__ ).parents[ 0 ]
+
+
+@registry.register_worker()
+class CoTReasoning( BaseLLMBackend, BaseWorker ):
+
+    prompts: List[ PromptTemplate ] = Field( default=[] )
+
+    def _run( self, id: int, query: str, cot_method: str, cot_examples: List[ dict ] = [], *args, **kwargs ):
+        """
+        Executes a reasoning task based on the specified Chain-of-Thought (CoT) method.
+        Args:
+            id (int): The identifier for the reasoning task.
+            query (str): The query string to be processed.
+            cot_method (str): The CoT method to use, either 'few_shot' or 'zero_shot'.
+            cot_examples (List[dict], optional): A list of examples for few-shot CoT. Defaults to an empty list.
+            *args: Additional positional arguments.
+            **kwargs: Additional keyword arguments.
+        Returns:
+            dict: A dictionary containing the task id, question, model output, prompt tokens, and completion tokens.
+        Raises:
+            ValueError: If an invalid CoT method is provided.
+        """
+
+        if cot_method == 'few_shot':
+            self.prompts = [
+                PromptTemplate.from_file( CURRENT_PATH.joinpath( "few_shot_cot.prompt" ), role="user" ),
+            ]
+
+            assert cot_examples, "Few-shot COT requires examples."
+
+            demo = CoTExample().create_examples( cot_examples )
+
+            res = self.simple_infer( query=query, demo=demo )
+
+            body = self.llm._msg2req( [ p for prompts in self.prep_prompt( [ { "query": query, "demo": demo} ] ) for p in prompts ] )
+        elif cot_method == 'zero_shot':
+            self.prompts = [
+                PromptTemplate.from_file( CURRENT_PATH.joinpath( "zero_shot_cot.prompt" ), role="user" ),
+            ]
+
+            res = self.simple_infer( query=query )
+
+            body = self.llm._msg2req( [ p for prompts in self.prep_prompt( [ { "query": query} ] ) for p in prompts ] )
+        else:
+            raise ValueError( f"Invalid cot_method: {cot_method}" )
+
+        # Extract the reasoning result from the response.
+        prompt_tokens = res[ 'usage' ][ 'prompt_tokens' ]
+        completion_tokens = res[ 'usage' ][ 'completion_tokens' ]
+        last_output = res[ "choices" ][ 0 ][ "message" ][ "content" ]
+        question = body.get( 'messages' )[ 0 ][ 'content' ][ 0 ][ 'text' ]
+
+        self.callback.send_answer(self.workflow_instance_id, msg=last_output)
+        return { 'id': id, 'question': question, 'last_output': last_output, 'prompt_tokens': prompt_tokens, "completion_tokens": completion_tokens}
diff --git a/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/few_shot_cot.prompt b/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/few_shot_cot.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..c7e92cc27f28088034034f18d9cd59e2982cae40
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/few_shot_cot.prompt
@@ -0,0 +1,4 @@
+{{demo}}
+
+Q: {{query}}
+A:
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/zero_shot_cot.prompt b/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/zero_shot_cot.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..f096f0e59f62c872c1cae97fc77422f9424485bd
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/cot/agent/cot_reasoning/zero_shot_cot.prompt
@@ -0,0 +1,2 @@
+Q: {{query}}
+A: Let's think step by step.
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/cot/schemas/__init__.py b/omagent_core/advanced_components/workflow/cot/schemas/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/cot/schemas/cot_create_examples.py b/omagent_core/advanced_components/workflow/cot/schemas/cot_create_examples.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd0176adbec4ef4fae4f9568c53af01356b0da37
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/cot/schemas/cot_create_examples.py
@@ -0,0 +1,36 @@
+from typing import List
+from pydantic import BaseModel, Field
+
+
+class CoTExample( BaseModel ):
+    demo: str = ''
+
+    def check_inpout( self, examples: List[ dict ] ) -> bool:
+        """
+        Checks if the input is a list of dictionaries with specific keys.
+        Args:
+            examples (List[dict]): A list of dictionaries to be checked.
+        Returns:
+            bool: True if the input is a list of dictionaries and each dictionary
+                    contains exactly the keys 'q', 'r', and 'a'. False otherwise.
+        """
+        if isinstance( examples, list ) and all(
+            isinstance( example, dict ) and
+            set( example.keys() ) == { 'q', 'r', 'a' } for example in examples
+        ):
+            return True
+        return False
+
+    def create_examples( self, cot_examples: List[ dict ] ) -> str:
+        """
+        Creates example strings from a list of dictionaries and appends them to the demo attribute.
+        Args:
+            examples (List[dict]): A list of dictionaries, each containing keys 'q' for question, 
+                                   'a' for answer, and 'r' for the result.
+        Returns:
+            str: The updated demo string with appended examples.
+        """
+        if self.check_inpout( cot_examples ):
+            demos = [f"Q: {example['q']}\nA: {example['r']} The answer is {example['a']}." for example in cot_examples]
+            self.demo = '\n\n'.join( demos )
+        return self.demo
diff --git a/omagent_core/advanced_components/workflow/cot/workflow.py b/omagent_core/advanced_components/workflow/cot/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..99f5e6c5923ab4ab56e270fb0d3610785973989c
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/cot/workflow.py
@@ -0,0 +1,55 @@
+from typing import List
+
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.simple_task import simple_task
+
+from omagent_core.advanced_components.workflow.cot.agent.cot_reasoning.cot_reasoning import CoTReasoning
+
+
+class CoTWorkflow( ConductorWorkflow ):
+
+    def __init__( self ):
+        super().__init__( name='cot_workflow' )
+
+    def set_input( self, query: str, cot_method: str = 'few_shot', cot_examples: List[ dict ] = None, id: int = 0 ):
+        """
+        Sets the input parameters for the workflow.
+        Args:
+            query (str): The query string to be processed.
+            cot_method (str, optional): The method to be used for chain-of-thought (CoT) processing. Defaults to 'few_shot'.
+            cot_examples (List[dict], optional): A list of examples for CoT processing. Defaults to None.
+            id (int, optional): An identifier for the workflow instance. Defaults to 0.
+        Returns:
+            None
+        """
+
+        self.id = id
+        self.query = query
+        self.cot_method = cot_method
+        self.cot_examples = cot_examples
+
+        self._configure_tasks()
+        self._configure_workflow()
+
+    def _configure_tasks( self ):
+        # reasoning task for reasoning process
+        self.reasoning_task = simple_task(
+            task_def_name=CoTReasoning,
+            task_reference_name='CoT_Reasoning',
+            inputs={
+                'id': self.id,
+                'query': self.query,
+                'cot_method': self.cot_method,
+                'cot_examples': self.cot_examples
+            }
+        )
+
+    def _configure_workflow( self ):
+        # configure workflow execution flow
+        self >> self.reasoning_task
+
+        self.id = self.reasoning_task.output( 'id' )
+        self.question = self.reasoning_task.output( 'question' )
+        self.last_output = self.reasoning_task.output( 'last_output' )
+        self.prompt_tokens = self.reasoning_task.output( 'prompt_tokens' )
+        self.completion_tokens = self.reasoning_task.output( 'completion_tokens' )
diff --git a/omagent_core/advanced_components/workflow/dnc/README.md b/omagent_core/advanced_components/workflow/dnc/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..cc5a6bc10f9a5f01eb6b022c0b953b4835288336
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/README.md
@@ -0,0 +1,43 @@
+# Divide-and-Conquer Operator
+Divide-and-Conquer (DnC) is a workflow operator that decomposes a complex task into multiple sub-tasks, and then conquers each sub-task to complete the original task. 
+
+You can refer to the example in the `examples/general_dnc` directory to understand how to use this operator.
+
+# Inputs, Outputs and configs
+
+## Inputs:
+The inputs that the Divide-and-Conquer (DnC) operator requires are as follows:
+| Name     | Type | Required | Description |
+| -------- | ----- | ----- | ---- |
+| query | str | true |  The name of the user |
+
+## Outputs:
+The outputs that the Divide-and-Conquer (DnC) operator returns are as follows:
+| Name     | Type | Description |
+| -------- | ----- | ---- |
+| dnc_structure | dict |  The tree structured data of the dnc workflow, including all (sub)tasks and their outputs. |
+| last_output | str |  The output of the last task. |
+
+## Configs:
+The config of the Divide-and-Conquer (DnC) operator is as follows, you can simply copy and paste the following config into your project as a dnc_workflow.yml file.
+```yml
+- name: ConstructDncPayload
+- name: StructureUpdate
+- name: TaskConqueror
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskDivider
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskRescue
+  llm: ${sub|text_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskExitMonitor
+
+```
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/dnc/__init__.py b/omagent_core/advanced_components/workflow/dnc/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/conqueror/__init__.py b/omagent_core/advanced_components/workflow/dnc/agent/conqueror/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/conqueror/conqueror.py b/omagent_core/advanced_components/workflow/dnc/agent/conqueror/conqueror.py
new file mode 100644
index 0000000000000000000000000000000000000000..bbbbf487003f8e3d9c56e45405df07181a8162ce
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/conqueror/conqueror.py
@@ -0,0 +1,237 @@
+import json
+import re
+from collections import defaultdict
+from pathlib import Path
+from typing import Any, List, Tuple
+
+import json_repair
+from colorama import Fore, Style
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import (
+    TaskStatus, TaskTree)
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.memories.ltms.ltm import LTM
+from omagent_core.models.llms.base import BaseLLMBackend, StrParser
+from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.tool_system.manager import ToolManager
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from tenacity import (retry, retry_if_exception_message, stop_after_attempt,
+                      stop_after_delay)
+from omagent_core.utils.logger import logging
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskConqueror(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    tool_manager: ToolManager
+
+    def _run(self, dnc_structure: dict, last_output: str, *args, **kwargs):
+        """A task conqueror that executes and manages complex task trees.
+
+        This component acts as a task conqueror that:
+        - Takes a hierarchical task tree and processes each task node
+        - Maintains context and state between task executions
+        - Leverages LLM to determine next actions and generate responses
+        - Manages task dependencies and relationships between parent/sibling tasks
+        - Tracks task completion status and progression through the tree
+
+        The conqueror is responsible for breaking down complex tasks into manageable
+        subtasks and ensuring they are executed in the correct order while maintaining
+        overall context and goal alignment.
+
+        Args:
+            dnc_structure (dict): The task tree definition and current state
+            last_output (str): The output from previous task execution
+            *args: Additional arguments
+            **kwargs: Additional keyword arguments
+
+        Returns:
+            dict: Processed response containing next actions or task completion results
+        """
+        task = TaskTree(**dnc_structure)
+        current_node = task.get_current_node()
+        current_node.status = TaskStatus.RUNNING
+
+        # Initialize former_results in shared memory if not present
+        # former_results is used to store the results of the tasks which are in the same depth that have been executed
+        if not self.stm(self.workflow_instance_id).get("former_results"):
+            self.stm(self.workflow_instance_id)["former_results"] = {}
+        payload = {
+            "task": current_node.task,
+            "tools": self.tool_manager.generate_prompt(),
+            "sibling_tasks": [
+                (
+                    {
+                        "task": each.task,
+                        "criticism": each.criticism,
+                        "milestones": each.milestones,
+                    }
+                    if each.id > current_node.id
+                    else None
+                )
+                for each in task.get_siblings(current_node.id)
+            ],
+            "parent_task": (
+                [
+                    {
+                        "task": each.task,
+                        "criticism": each.criticism,
+                        "milestones": each.milestones,
+                    }
+                    for each in [task.get_parent(current_node.id)]
+                ][0]
+                if task.get_parent(current_node.id)
+                else []
+            ),
+            "former_results": self.stm(self.workflow_instance_id)["former_results"],
+            "extra_info": self.stm(self.workflow_instance_id).get("extra"),
+            "img_placeholders": self.stm(self.workflow_instance_id).get("image_cache"),
+        }
+
+        # Call LLM to get next actions or task completion results
+        chat_complete_res = self.infer(input_list=[payload])
+        logging.info(f"Conqueror chat_complete_res: {chat_complete_res}")
+        content = chat_complete_res[0]["choices"][0]["message"].get("content")
+        content = json_repair.loads(content)
+
+        # Handle the case where the LLM returns a dictionary with multiple keys
+        first_key = next(iter(content))
+        new_data = {first_key: content[first_key]}
+        content = new_data
+
+        # LLM returns the direct answer of the task
+        if content.get("agent_answer"):
+            last_output = content
+            if task.get_parent(current_node.id):
+                if current_node.id not in [
+                    each.id
+                    for each in task.get_children(task.get_parent(current_node.id).id)
+                ]:
+                    self.stm(self.workflow_instance_id)["former_results"] = {}
+            former_results = self.stm(self.workflow_instance_id)["former_results"]
+            former_results[current_node.task] = content
+            self.stm(self.workflow_instance_id)["former_results"] = former_results
+            current_node.result = content["agent_answer"]
+            current_node.status = TaskStatus.SUCCESS
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Conqueror",
+                message=f'Task "{current_node.task}"\nAgent answer: {content["agent_answer"]}',
+            )
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "switch_case_value": "success",
+                "last_output": last_output,
+                "kwargs": kwargs,
+            }
+
+        # LLM returns the reason why the task needs to be divided
+        elif content.get("divide"):
+            current_node.result = content["divide"]
+            current_node.status = TaskStatus.RUNNING
+            last_output = (
+                "Task is too complex to complete. Agent provided reason: {}".format(
+                    content["divide"]
+                )
+            )
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Conqueror",
+                message=f'Task "{current_node.task}" needs to be divided.',
+            )
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "switch_case_value": "complex",
+                "last_output": last_output,
+                "kwargs": kwargs,
+            }
+
+        # LLM returns the tool call information
+        elif content.get("tool_call"):
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Conqueror",
+                message=f'Task "{current_node.task}" needs to be executed by tool.',
+            )
+
+            # Call tool_manager to decide which tool to use and execute the tool
+            execution_status, execution_results = self.tool_manager.execute_task(
+                content["tool_call"],
+                related_info=self.stm(self.workflow_instance_id)["former_results"],
+            )
+            former_results = self.stm(self.workflow_instance_id)["former_results"]
+            former_results["tool_call"] = content["tool_call"]
+
+            # Handle the case where the tool call is successful
+            if execution_status == "success":
+                last_output = execution_results
+                if task.get_parent(current_node.id):
+                    if current_node.id not in [
+                        each.id
+                        for each in task.get_children(
+                            task.get_parent(current_node.id).id
+                        )
+                    ]:
+                        self.stm(self.workflow_instance_id)["former_results"] = {}
+                former_results.pop("tool_call", None)
+                former_results[current_node.task] = execution_results
+                self.stm(self.workflow_instance_id)["former_results"] = former_results
+                current_node.result = execution_results
+                current_node.status = TaskStatus.SUCCESS
+                toolcall_success_output_structure = {
+                    "tool_status": current_node.status,
+                    "tool_result": current_node.result,
+                }
+                self.callback.info(
+                    agent_id=self.workflow_instance_id,
+                    progress=f"Conqueror",
+                    message=f"Tool call success. {toolcall_success_output_structure}",
+                )
+                self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+                self.stm(self.workflow_instance_id)["last_output"] = last_output
+                return {
+                    "dnc_structure": task.model_dump(),
+                    "switch_case_value": "success",
+                    "last_output": last_output,
+                    "kwargs": kwargs,
+                }
+            # Handle the case where the tool call is failed
+            else:
+                current_node.result = execution_results
+                current_node.status = TaskStatus.FAILED
+                former_results["tool_call_error"] = (
+                    f"tool_call {content['tool_call']} raise error: {current_node.result}"
+                )
+                self.stm(self.workflow_instance_id)["former_results"] = former_results
+                self.callback.info(
+                    agent_id=self.workflow_instance_id,
+                    progress=f"Conqueror",
+                    message=f'Tool call failed. {former_results["tool_call_error"]}',
+                )
+                self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+                self.stm(self.workflow_instance_id)["last_output"] = last_output
+                return {
+                    "dnc_structure": task.model_dump(),
+                    "switch_case_value": "failed",
+                    "last_output": last_output,
+                    "kwargs": kwargs,
+                }
+        # Handle the case where the LLM generation is not valid
+        else:
+            raise ValueError("LLM generation is not valid.")
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/conqueror/sys_prompt.prompt b/omagent_core/advanced_components/workflow/dnc/agent/conqueror/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..0ee1e3a637a440fccd2ef958154bf3db980d4edc
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/conqueror/sys_prompt.prompt
@@ -0,0 +1,44 @@
+You are the most powerful AI agent with the ability to see images. As a Super Agent build with super powerful tools, you are capable of handling any given task, thus your capabilities are far above regular simple AI or LLM.
+
+--- Output ---
+The output should be a dict in json format, key is one of "divide", "tool_call" and "agent_answer":
+"divide": string. The subtasks that need to be done if you want to complete the current task or current information can't answer current task precisely. If the goal of the task cannot be achieved using a single tool or agent answer is vague, must use this field. Be sure to pay attention to each milestone of the task and ensure that all of them are achieved. Bring as much detail as possible to the task.
+
+"tool_call": string. The task needs more specific details by calling a single tool that listed in Tools section. This value must be a comprehensive and detailed description of the task.
+
+"agent_answer": string. The task can be answered directly by the agent without any tools in precisely and detailed, rather than vague and general.
+{
+    "divide" or "tool_call" or "agent_answer": "string"
+}
+
+--- Tools ---
+{{tools}}
+
+--- Examples ---
+1.
+Task: Write the price of the book in this url 'books.toscrape.com/catalogue/meditations_33/index.html' to a .txt file.
+Result: {"divide":"This task involves accessing 'books.toscrape.com/catalogue/meditations_33/index.html' to retrieve book pricing information and then writing that information to a text file, which requires the use of internet access and file manipulation tools."}
+
+2.
+Task: 1- Run code.py.\n2- Read test.py.\n3- Modify test.py.\nRepeat step 1, 2 and 3 until code.py runs without errors.\n
+Result:{"divide":"The task requires iterative execution, reading, and modification of code which cannot be accomplished in a single step and needs to be broken down into subtasks."}
+
+3.
+Task: Add a function called multiply_int in code.py that multiplies numbers by 2. You can make sure you have correctly done this by running test.py
+Result: {"divide":"The task requires modifying a Python file to add a new function, and then running another Python file to test the correctness of the implementation. This involves code editing and execution which cannot be completed without the use of file manipulation and a Python interpreter."}
+4.
+Task: At the end of the video, who threw the winning shot?
+Result: {"divide":" Based on the current summary information from the video and the image information, I am unable to obtain the information about who scored the winning goal in the end. I need to have more detailed frame-by-frame images and text information from the end of the video in order to answer this question correctly."}
+
+5.
+Task: Who is the actor playing the character that appears at 3 minutes and 12 seconds in the video?
+Result:{"divide":" I need to first extract a frame from the video at 3 minutes and 12 seconds, and then analyze the characters in the image."}
+
+*** Important Notice ***
+- You must follow your workflow.
+- You are more than a Large Language Model (LLM), you have the capability to do actual things rather than simply give guidance or write text.
+- With the help of tools, you can do nearly everything. You can not only write guides or code, but can also use the tools to actually do the task like setup service or develop complex program.
+- If the task is something like text understanding, classification, analyzing, etc, output your answer directly, don't use external tools or execute codes.
+- You can only give up when the tools are insufficient.
+- You must pass literal and concrete value to tools, do not use reference or pointer, labels.
+- You can doing any task with the help of given tools, even developing complex programs or configure complex develop environment.
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/conqueror/user_prompt.prompt b/omagent_core/advanced_components/workflow/dnc/agent/conqueror/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..91c2f43852c2fee0cf3eaabf13ad379103141c2a
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/conqueror/user_prompt.prompt
@@ -0,0 +1,10 @@
+Now, it's your turn to complete the task.
+--- Status ---
+former results (The result from former subtask. You must take this part into consideration.): {{former_results}}
+Current task (The task you need to complete.): {{task}}
+Sibling tasks (Your task is one part of a larger task. Sibling tasks includes all other subtasks. You can get some big picture from them.): {{sibling_tasks}}
+Parent task (Current tasks is a subtask of the parent task. If this is empty, current task is the original task.): {{parent_task}}
+Extra information: {{extra_info}}
+Related images: {{img_placeholders}}
+
+Now show your super capability as a super agent that beyond regular AIs or LLMs!
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/construct_dnc_payload/construct_dnc_payload.py b/omagent_core/advanced_components/workflow/dnc/agent/construct_dnc_payload/construct_dnc_payload.py
new file mode 100644
index 0000000000000000000000000000000000000000..9746d1506c8c6c441148eeafa8185e10f98152dd
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/construct_dnc_payload/construct_dnc_payload.py
@@ -0,0 +1,12 @@
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import \
+    TaskTree
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+
+
+@registry.register_worker()
+class ConstructDncPayload(BaseWorker):
+    def _run(self, query: str):
+        tree = TaskTree()
+        tree.add_node({"task": query})
+        return {"dnc_structure": tree.model_dump()}
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/divider/__init__.py b/omagent_core/advanced_components/workflow/dnc/agent/divider/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/divider/divider.py b/omagent_core/advanced_components/workflow/dnc/agent/divider/divider.py
new file mode 100644
index 0000000000000000000000000000000000000000..d615fa86b74be3a2a6b7c10d0e35ee42ed701ce0
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/divider/divider.py
@@ -0,0 +1,140 @@
+import json
+import re
+from pathlib import Path
+from typing import Any, List, Tuple
+
+import json_repair
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import \
+    TaskTree
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.tool_system.manager import ToolManager
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from tenacity import (retry, retry_if_exception_message, stop_after_attempt,
+                      stop_after_delay)
+from omagent_core.utils.logger import logging
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskDivider(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    tool_manager: ToolManager
+    max_task_depth: int = 5
+
+    def _run(self, dnc_structure: dict, last_output: str, *args, **kwargs):
+        """Task divider that breaks down complex tasks into multiple subtasks.
+
+        Args:
+            dnc_structure (dict): Dictionary containing the current task tree information
+            last_output (str): Output from the previous task execution
+            *args: Variable positional arguments
+            **kwargs: Variable keyword arguments
+
+        Returns:
+            dict: A dictionary containing:
+                - dnc_structure: Updated task tree
+                - switch_case_value: Task division status ("success"/"failed")
+                - last_output: Latest output result
+                - kwargs: Additional keyword arguments
+
+        Description:
+            1. Checks if task depth exceeds maximum limit
+            2. Calls LLM to divide current task into subtasks
+            3. Updates task tree structure
+            4. Returns division results and status
+        """
+        task = TaskTree(**dnc_structure)
+        current_node = task.get_current_node()
+        # Check if task depth exceeds maximum limit
+        if task.get_depth(current_node.id) >= self.max_task_depth:
+            last_output = "failed: Max subtask depth reached"
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Divider",
+                message=f"Max subtask depth reached.",
+            )
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "switch_case_value": "failed",
+                "last_output": last_output,
+                "kwargs": kwargs,
+            }
+
+        # Call LLM to divide current task into subtasks
+        chat_complete_res = self.simple_infer(
+            parent_task=current_node.task,
+            uplevel_tasks=(
+                task.get_parent(current_node.id)
+                if task.get_parent(current_node.id)
+                else []
+            ),
+            former_results=last_output,
+            tools=self.tool_manager.generate_prompt(),
+        )
+        logging.info(f"Divider chat_complete_res: {chat_complete_res}")
+        chat_complete_res = json_repair.loads(
+            chat_complete_res["choices"][0]["message"]["content"]
+        )
+
+        # Update task tree structure
+        if chat_complete_res.get("tasks"):
+            task.add_subtasks(current_node.id, chat_complete_res["tasks"])
+            subtasks_info = "\n".join(
+                [
+                    f"{idx}: {each.task}"
+                    for idx, each in enumerate(task.get_children(current_node.id))
+                ]
+            )
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Divider",
+                message=f'Task "{current_node.task}" has been divided into {len(task.get_children(current_node.id))} subtasks: \n{subtasks_info}',
+            )
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "switch_case_value": "success",
+                "last_output": last_output,
+                "kwargs": kwargs,
+            }
+
+        # Handle failed task division
+        elif chat_complete_res.get("failed_reason"):
+            last_output = (
+                "failed: Subtask generation failed. Agent provided reason: {}".format(
+                    chat_complete_res.get("failed_reason", "No reason generated.")
+                )
+            )
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Divider",
+                message=f"Subtask generation failed.",
+            )
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "switch_case_value": "failed",
+                "last_output": last_output,
+                "kwargs": kwargs,
+            }
+
+        # Handle invalid LLM generation
+        else:
+            raise ValueError("LLM generation is not valid.")
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/divider/sys_prompt.prompt b/omagent_core/advanced_components/workflow/dnc/agent/divider/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..450f4a8d393716a3d7484eda193b785d51824425
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/divider/sys_prompt.prompt
@@ -0,0 +1,40 @@
+You are an efficient plan-generation agent.
+Your job is to decompose an overly complex task into multiple subtasks based on the available tools.
+These subtasks need to be precise and include all relevant information required to complete them.
+Another agent will execute these subtasks, and you both share all the tools.
+Your workflow follows the principle of "divide and conquer," so the subtasks must be clear and executable.
+A conclusion subtask must exist at the end of the subtasks list, providing a reasonable answer and explanation for the parent task.
+
+--- Output Structure ---
+The output result should follows this json structure:
+{
+    "tasks": List[SubTask]. The list of SubTask object. The subtasks are decomposed from the parent task. These subtasks should be executed sequentially and ultimately achieve the desired result of the parent task. If you can not decompose the parent task for some reasons, leave this field a empty list and put the reason in 'failed_reason' field.
+    "failed_reason": string. The reason why you can not generate subtasks. Only provide when the 'tasks' field is an empty list, else return empty string "".
+}
+SubTask object json structure:
+{
+    "task": string. The purposes of the sub-task should handle, and detailed implementation path.
+    "criticism": string. What problems may the current subtask and goal have.
+    "milestones": list[string]. How can we automatically verify the completion of each sub-task?
+    "status": string. Indicates the status of this task. Enum of ['running', 'waiting', 'success', 'failed']. 'running' for the now executing; 'waiting' for tasks waiting for executing; 'success' for the successfully finished this and 'failed' for this executing failed.
+}
+
+--- Background Information ---
+1. uplevel tasks: The tasks series of the level of the parent task. Structured in List[SubTask] format. This could be empty when the task is the original task.
+2. former results: The result generated by the former subtask. This could be empty if the parent task is the first task in it's level.
+
+--- Available Tools ---
+{{tools}}
+
+--- Note ---
+The user is busy, so make efficient plans that can lead to successful task solving.
+Do not waste time on making irrelevant or unnecessary plans.
+Don't divide trivial task into multiple steps.
+If task is un-solvable, give up and return with the reason.
+
+*** Important Notice ***
+- Think step by step. Do not omit any necessary subtasks and do not plan unnecessary subtasks.
+- Never create new subtasks that similar or same as the existing subtasks or executed tasks.
+- For subtasks with similar goals, try to merge them together in one subtask.
+- The task handler is powered by SOTA LLM and enormous tools, which can directly solve any subtasks. So make sure your plan can fully utilize its ability and reduce the complexity of the subtasks tree.
+- The output should strictly adhere to the given output structure.
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/divider/user_prompt.prompt b/omagent_core/advanced_components/workflow/dnc/agent/divider/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..1376bc77a975df5a193cb6ec8bcfbb879284695a
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/divider/user_prompt.prompt
@@ -0,0 +1,3 @@
+father task: {{parent_task}}
+uplevel tasks: {{uplevel_tasks}}
+former results: {{former_results}}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/structure_update/structure_update.py b/omagent_core/advanced_components/workflow/dnc/agent/structure_update/structure_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..96625e9cea44baad1794e9dcb8e7d9d45e7a3987
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/structure_update/structure_update.py
@@ -0,0 +1,20 @@
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+
+
+@registry.register_worker()
+class StructureUpdate(BaseWorker):
+    def _run(self, dnc_structure: dict, last_output: str=None, *args, **kwargs):
+        stm_data = self.stm(self.workflow_instance_id)
+        stm_dnc_structure = stm_data.get("dnc_structure", None)
+        stm_last_output = stm_data.get("last_output", None)
+        if stm_dnc_structure is None:
+            stm_dnc_structure = dnc_structure
+            stm_data["dnc_structure"] = stm_dnc_structure
+            self.stm(self.workflow_instance_id)["dnc_structure"] = stm_dnc_structure
+        if stm_last_output is None:
+            stm_last_output = last_output
+            stm_data["last_output"] = stm_last_output
+            self.stm(self.workflow_instance_id)["last_output"] = stm_last_output
+
+        return {"dnc_structure": stm_dnc_structure, "last_output": stm_last_output}
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/task_exit_monitor/__init__.py b/omagent_core/advanced_components/workflow/dnc/agent/task_exit_monitor/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/task_exit_monitor/task_exit_monitor.py b/omagent_core/advanced_components/workflow/dnc/agent/task_exit_monitor/task_exit_monitor.py
new file mode 100644
index 0000000000000000000000000000000000000000..65586acd49f61ab2707be6095360569ff44c3ef3
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/task_exit_monitor/task_exit_monitor.py
@@ -0,0 +1,110 @@
+import json
+import re
+from pathlib import Path
+from typing import Any, List, Tuple
+
+import json_repair
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import \
+    TaskTree
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.memories.ltms.ltm import LTM
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.tool_system.manager import ToolManager
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from tenacity import (retry, retry_if_exception_message, stop_after_attempt,
+                      stop_after_delay)
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskExitMonitor(BaseWorker):
+
+    def _run(self, *args, **kwargs):
+        """A task exit monitor that determines task completion and loop exit conditions.
+
+        This component acts as a monitor that:
+        - Takes a task tree and checks the status of each task node
+        - Determines if the current task branch is complete
+        - Decides whether to continue to next sibling/child task or exit
+        - Manages the traversal and completion of the entire task tree
+        - Controls the dynamic task execution loop
+
+        The monitor is responsible for:
+        1. Checking if current task failed -> exit loop
+        2. If current task has children -> move to first child
+        3. If current task has next sibling -> move to next sibling
+        4. If at root with no siblings -> exit loop
+        5. If parent has no next sibling -> exit loop
+        6. Otherwise -> move to parent's next sibling
+
+        Args:
+            dnc_structure (dict): The task tree containing all tasks and their status
+            last_output (str): The output from previous task execution
+            *args: Additional arguments
+            **kwargs: Additional keyword arguments
+
+        Returns:
+            dict: Contains updated task tree, exit flag, and last output
+        """
+        stm_data = self.stm(self.workflow_instance_id)
+        stm_dnc_structure = stm_data.get("dnc_structure", None)
+        stm_last_output = stm_data.get("last_output", None)
+        task = TaskTree(**stm_dnc_structure)
+        current_node = task.get_current_node()
+        if current_node.status == "failed":
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = stm_last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "exit_flag": True,
+                "last_output": stm_last_output,
+            }
+        elif task.get_children(current_node.id) != []:
+            task.set_cursor(task.get_children(current_node.id)[0].id)
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = stm_last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "exit_flag": False,
+                "last_output": stm_last_output,
+            }
+        elif task.get_next_sibling(current_node.id) is not None:
+            task.set_cursor(task.get_next_sibling(current_node.id).id)
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = stm_last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "exit_flag": False,
+                "last_output": stm_last_output,
+            }
+        else:
+            if task.get_parent(current_node.id) is None:
+                self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+                self.stm(self.workflow_instance_id)["last_output"] = stm_last_output
+                return {
+                    "dnc_structure": task.model_dump(),
+                    "exit_flag": True,
+                    "last_output": stm_last_output,
+                }
+            elif task.get_next_sibling(task.get_parent(current_node.id).id) is None:
+                self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+                self.stm(self.workflow_instance_id)["last_output"] = stm_last_output
+                return {
+                    "dnc_structure": task.model_dump(),
+                    "exit_flag": True,
+                    "last_output": stm_last_output,
+                }
+            else:
+                task.set_cursor(
+                    task.get_next_sibling(task.get_parent(current_node.id).id).id
+                )
+                self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+                self.stm(self.workflow_instance_id)["last_output"] = stm_last_output
+                return {
+                    "dnc_structure": task.model_dump(),
+                    "exit_flag": False,
+                    "last_output": stm_last_output,
+                }
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/rescue.py b/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/rescue.py
new file mode 100644
index 0000000000000000000000000000000000000000..521ab6bd18a2ede1cab66a89b571aa258862c984
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/rescue.py
@@ -0,0 +1,139 @@
+import json
+from pathlib import Path
+from typing import List, Tuple
+
+from colorama import Fore, Style
+from omagent_core.advanced_components.workflow.dnc.schemas.dnc_structure import (
+    TaskStatus, TaskTree)
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.memories.ltms.ltm import LTM
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.tool_system.manager import ToolManager
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from tenacity import (retry, retry_if_exception_message, stop_after_attempt,
+                      stop_after_delay)
+
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskRescue(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    tool_manager: ToolManager
+
+    def _run(self, dnc_structure: dict, last_output: str, *args, **kwargs):
+        """A task rescue node that attempts to recover from tool execution failures.
+
+        This component acts as a rescue mechanism that:
+        - Takes a failed tool execution and its error details
+        - Analyzes the failure reason (e.g. missing dependencies, invalid arguments)
+        - Attempts to fix the issue by retrying with corrected parameters
+        - Maintains context about the original task and failure
+        - Provides detailed feedback about rescue attempts
+
+        The rescue node is responsible for:
+        1. Retrieving the original failed tool call and error details
+        2. Using LLM to understand the failure and generate fixes
+        3. Re-executing the tool with corrections
+        4. Tracking rescue attempts and results
+        5. Determining if rescue was successful or needs further handling
+
+        Args:
+            dnc_structure (dict): The task tree containing the failed task
+            last_output (str): The output from previous failed execution
+            *args: Additional arguments
+            **kwargs: Additional keyword arguments
+
+        Returns:
+            dict: Contains updated task status, rescue results, and next actions
+        """
+        toolcall_content = (
+            self.stm(self.workflow_instance_id)
+            .get("former_results", {})
+            .pop("tool_call", None)
+        )
+        # Get the tool call information from the former results
+        if toolcall_content is not None:
+            former_results = self.stm(self.workflow_instance_id)["former_results"]
+            tool_call_error = former_results.pop("tool_call_error", None)
+            task = TaskTree(**dnc_structure)
+            current_node = task.get_current_node()
+
+            # Call LLM to understand the failure and generate fixes
+            chat_complete_res = self.simple_infer(
+                task=current_node.task,
+                failed_detail=tool_call_error,
+            )
+            former_results["failed_detail"] = chat_complete_res["choices"][0][
+                "message"
+            ]["content"]
+
+            # Re-execute the tool call with the corrected parameters
+            rescue_execution_status, rescue_execution_results = (
+                self.tool_manager.execute_task(
+                    toolcall_content, related_info=former_results
+                )
+            )
+
+            # Handle the case where the rescue execution is successful
+            if rescue_execution_status == "success":
+                former_results.pop("failed_detail", None)
+                former_results["rescue_detail"] = rescue_execution_results
+                self.stm(self.workflow_instance_id)["former_results"] = former_results
+                self.callback.info(
+                    agent_id=self.workflow_instance_id,
+                    progress=f"Rescue",
+                    message=f"Rescue tool call success.",
+                )
+                self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+                self.stm(self.workflow_instance_id)["last_output"] = last_output
+                return {
+                    "dnc_structure": task.model_dump(),
+                    "switch_case_value": "success",
+                    "last_output": last_output,
+                    "kwargs": kwargs,
+                }
+            # Handle the case where the rescue execution is failed
+            else:
+                self.stm(self.workflow_instance_id)["former_results"] = former_results
+                current_node.status = TaskStatus.RUNNING
+                self.callback.info(
+                    agent_id=self.workflow_instance_id,
+                    progress=f"Rescue",
+                    message=f"Rescue tool call failed.",
+                )
+                self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+                self.stm(self.workflow_instance_id)["last_output"] = last_output
+                return {
+                    "dnc_structure": task.model_dump(),
+                    "switch_case_value": "failed",
+                    "last_output": last_output,
+                    "kwargs": kwargs,
+                }
+
+        # Handle the case where there is no tool call to rescue
+        else:
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Rescue",
+                message=f"No tool call to rescue.",
+            )
+            self.stm(self.workflow_instance_id)["dnc_structure"] = task.model_dump()
+            self.stm(self.workflow_instance_id)["last_output"] = last_output
+            return {
+                "dnc_structure": task.model_dump(),
+                "switch_case_value": "failed",
+                "last_output": last_output,
+                "kwargs": kwargs,
+            }
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/sys_prompt.prompt b/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..84e5c38ccb1e926cf702d33623162d5451e1e045
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/sys_prompt.prompt
@@ -0,0 +1,5 @@
+As the rescue section, your role is to try your best to identify and fix the error in provided failed detail.
+Your task includes three parts:
+1. Based on the original question, analyze whether the current operation can meet the requirements of the question.
+2. Based on the provided failed detail, analyze the problem and consider all possible solutions.
+3. Select the best solution from all possible solutions and provide the modified **correct** answer.
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/user_prompt.prompt b/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..8028c919fdb0efd927fbf8483d2432b3d81c5650
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/agent/task_rescue/user_prompt.prompt
@@ -0,0 +1,6 @@
+Now, it's your turn to complete the task.
+
+Task (The task you need to complete.): {{task}}
+Failed detail (The result from former agents.): {{failed_detail}}
+
+Now show your super capability as a super agent that beyond regular AIs or LLMs!
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/dnc/schemas/dnc_structure.py b/omagent_core/advanced_components/workflow/dnc/schemas/dnc_structure.py
new file mode 100644
index 0000000000000000000000000000000000000000..86c49a6550b069ed389bf94148bd3b5694d4c82b
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/schemas/dnc_structure.py
@@ -0,0 +1,141 @@
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel, Field
+
+
+class TaskStatus(str, Enum):
+    WAITING = "waiting"
+    RUNNING = "running"
+    SUCCESS = "success"
+    FAILED = "failed"
+
+
+class TaskNode(BaseModel):
+    id: int
+    parent_id: Optional[int] = None
+    task: str
+    criticism: str = "None"
+    milestones: List[str] = []
+    status: TaskStatus = TaskStatus.WAITING
+    result: Any = None
+
+
+class TaskTree(BaseModel):
+    nodes: Dict[int, TaskNode] = Field(default_factory=dict)
+    cursor: int = 0
+    next_id: int = 0
+
+    def add_node(self, task: dict, parent_id: Optional[int] = None) -> TaskNode:
+        """添加单个节点到树中"""
+        node = TaskNode(id=self.next_id, parent_id=parent_id, **task)
+        self.nodes[node.id] = node
+        self.next_id += 1
+        return node
+
+    def add_subtasks(self, parent_id: int, subtasks: List[dict]) -> List[TaskNode]:
+        """为指定节点添加子任务"""
+        if parent_id not in self.nodes:
+            raise ValueError(f"Parent node {parent_id} not found")
+
+        added_nodes = []
+        for subtask in subtasks:
+            node = self.add_node(subtask, parent_id=parent_id)
+            added_nodes.append(node)
+        return added_nodes
+
+    def get_children(self, node_id: int) -> List[TaskNode]:
+        """获取指定节点的所有子节点"""
+        return [node for node in self.nodes.values() if node.parent_id == node_id]
+
+    def get_parent(self, node_id: int) -> Optional[TaskNode]:
+        """获取指定节点的父节点"""
+        node = self.nodes.get(node_id)
+        if node and node.parent_id is not None:
+            return self.nodes.get(node.parent_id)
+        return None
+
+    def get_siblings(self, node_id: int) -> List[TaskNode]:
+        """获取指定节点的兄弟节点(不包括自己)"""
+        node = self.nodes.get(node_id)
+        if not node:
+            return []
+        if node.parent_id is None:
+            return []
+        return [
+            n
+            for n in self.nodes.values()
+            if n.parent_id == node.parent_id and n.id != node_id
+        ]
+
+    def get_next_sibling(self, node_id: int) -> Optional[TaskNode]:
+        """获取下一个兄弟节点"""
+        siblings = self.get_siblings(node_id)
+        siblings.sort(key=lambda x: x.id)
+        for i, sibling in enumerate(siblings):
+            if sibling.id == node_id + 1 and i <= len(siblings) - 1:
+                return siblings[i]
+        return None
+
+    def get_previous_sibling(self, node_id: int) -> Optional[TaskNode]:
+        """获取上一个兄弟节点"""
+        siblings = self.get_siblings(node_id)
+        siblings.sort(key=lambda x: x.id)
+        for i, sibling in enumerate(siblings):
+            if sibling.id == node_id - 1 and i >= 0:
+                return siblings[i]
+        return None
+
+    def get_root(self) -> Optional[TaskNode]:
+        """获取根节点"""
+        for node in self.nodes.values():
+            if node.parent_id is None:
+                return node
+        return None
+
+    def get_depth(self, node_id: int) -> int:
+        """获取节点的深度(从1开始)"""
+        depth = 1
+        current = self.nodes.get(node_id)
+        while current and current.parent_id is not None:
+            current = self.nodes.get(current.parent_id)
+            depth += 1
+        return depth
+
+    def get_current_node(self) -> Optional[TaskNode]:
+        """获取当前光标所在的节点"""
+        return self.nodes.get(self.cursor)
+
+    def set_cursor(self, node_id: int):
+        """设置光标位置"""
+        if node_id in self.nodes:
+            self.cursor = node_id
+        else:
+            raise ValueError(f"Node {node_id} not found")
+
+
+if __name__ == "__main__":
+    import json
+
+    # 测试代码
+    tree = TaskTree()
+
+    # 添加根任务
+    root = tree.add_node(
+        {"task": "search hangzhou weather tomorrow and save the result into a file"}
+    )
+
+    # 添加子任务
+    subtasks = [
+        {"task": "search hangzhou weather tomorrow"},
+        {"task": "analyze the weather data"},
+        {"task": "save the result into a file"},
+    ]
+    children = tree.add_subtasks(root.id, subtasks)
+
+    sub_task1 = tree.get_children(root.id)[1]
+    sub_task0 = tree.get_previous_sibling(sub_task1.id)
+    sub_task2 = tree.get_next_sibling(sub_task1.id)
+    tree.get_next_sibling(0)
+    tree.get_root()
+    print()
diff --git a/omagent_core/advanced_components/workflow/dnc/workflow.py b/omagent_core/advanced_components/workflow/dnc/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..52383ea4c2e5cd5bb2d4da994c8a69d8e01941be
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/dnc/workflow.py
@@ -0,0 +1,98 @@
+from omagent_core.advanced_components.workflow.dnc.agent.conqueror.conqueror import \
+    TaskConqueror
+from omagent_core.advanced_components.workflow.dnc.agent.construct_dnc_payload.construct_dnc_payload import \
+    ConstructDncPayload
+from omagent_core.advanced_components.workflow.dnc.agent.divider.divider import \
+    TaskDivider
+from omagent_core.advanced_components.workflow.dnc.agent.structure_update.structure_update import \
+    StructureUpdate
+from omagent_core.advanced_components.workflow.dnc.agent.task_exit_monitor.task_exit_monitor import \
+    TaskExitMonitor
+from omagent_core.advanced_components.workflow.dnc.agent.task_rescue.rescue import \
+    TaskRescue
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.do_while_task import DnCLoopTask
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.engine.workflow.task.switch_task import SwitchTask
+
+
+class DnCWorkflow(ConductorWorkflow):
+    def __init__(self):
+        super().__init__(name="dnc_workflow")
+
+    def set_input(self, query: str):
+        self.query = query
+        self._configure_tasks()
+        self._configure_workflow()
+
+    def _configure_tasks(self):
+        # construct input query into dnc tree structure
+        self.construct_dnc_payload_task = simple_task(
+            task_def_name=ConstructDncPayload,
+            task_reference_name="construct_dnc_payload",
+            inputs={"query": self.query},
+        )
+
+        # this task is to update dnc tree structure using stm
+        self.structure_update_task = simple_task(
+            task_def_name=StructureUpdate,
+            task_reference_name="structure_update",
+            inputs={
+                "dnc_structure": self.construct_dnc_payload_task.output("dnc_structure")
+            },
+        )
+        # conqueror task for task generation
+        self.conqueror_task = simple_task(
+            task_def_name=TaskConqueror,
+            task_reference_name="task_conqueror",
+            inputs={
+                "dnc_structure": self.structure_update_task.output("dnc_structure"),
+                "last_output": self.structure_update_task.output("last_output"),
+            },
+        )
+
+        # divider task for task division
+        self.divider_task = simple_task(
+            task_def_name=TaskDivider,
+            task_reference_name="task_divider",
+            inputs={
+                "dnc_structure": self.conqueror_task.output("dnc_structure"),
+                "last_output": self.conqueror_task.output("last_output"),
+            },
+        )
+
+        # rescue task for task rescue
+        self.rescue_task = simple_task(
+            task_def_name=TaskRescue,
+            task_reference_name="task_rescue",
+            inputs={
+                "dnc_structure": self.conqueror_task.output("dnc_structure"),
+                "last_output": self.conqueror_task.output("last_output"),
+            },
+        )
+
+        # wwitch task for task routing
+        self.switch_task = SwitchTask(
+            task_ref_name="switch_task",
+            case_expression=self.conqueror_task.output("switch_case_value"),
+        )
+        self.switch_task.switch_case("complex", self.divider_task)
+        self.switch_task.switch_case("failed", self.rescue_task)
+
+        # task exit monitor task for task exit monitoring
+        self.task_exit_monitor_task = simple_task(
+            task_def_name=TaskExitMonitor, task_reference_name="task_exit_monitor"
+        )
+
+        # DnC loop task for task loop
+        self.dncloop_task = DnCLoopTask(
+            task_ref_name="dncloop_task",
+            tasks=[self.structure_update_task, self.conqueror_task, self.switch_task],
+            post_loop_exit=[self.task_exit_monitor_task],
+        )
+
+    def _configure_workflow(self):
+        # configure workflow execution flow
+        self >> self.construct_dnc_payload_task >> self.dncloop_task
+        self.dnc_structure = self.task_exit_monitor_task.output("dnc_structure")
+        self.last_output = self.task_exit_monitor_task.output("last_output")
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/concluder/concluder.py b/omagent_core/advanced_components/workflow/general_got/agent/concluder/concluder.py
new file mode 100644
index 0000000000000000000000000000000000000000..6465f0c084063fc7eb4f8b8fa8d87d7cd6e961a1
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/concluder/concluder.py
@@ -0,0 +1,81 @@
+import json
+import re
+from pathlib import Path
+from typing import List, Tuple, Any
+
+from pydantic import Field
+from tenacity import (
+    retry,
+    retry_if_exception_message,
+    stop_after_attempt,
+    stop_after_delay,
+)
+
+from omagent_core.utils.env import EnvVar
+from omagent_core.utils.registry import registry
+from omagent_core.utils.logger import logging
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.advanced_components.workflow.general_got.schemas.got_structure import TaskTree, TaskNode
+import json_repair
+from omagent_core.advanced_components.workflow.general_got.agent.utils import sort_num_errors, sort_parse_refine_answer
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskConcluder(BaseLLMBackend, BaseWorker):
+    # prompts: List[PromptTemplate] = Field(
+    #     default=[
+    #         PromptTemplate.from_file(
+    #             CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+    #         ),
+    #         PromptTemplate.from_file(
+    #             CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+    #         ),
+    #     ]
+    # )
+
+    def _run(self, *args, **kwargs):
+        """
+        Execute the task conclusion process using LLM.
+        
+        This method:
+        1. Retrieves the query and task tree from the state management
+        2. Identifies the current executable nodes in the last phase
+        3. Processes the final output based on special task types
+        4. Reports token usage statistics
+        
+        Returns:
+            dict: Contains the final output and token usage information
+        """
+        query = self.stm(self.workflow_instance_id)['query']
+        task_tree = self.stm(self.workflow_instance_id)['task_tree']
+
+        current_nodes = []
+        for id in task_tree.leaves:
+            node = task_tree.get_node(id)
+            can_be_executed = task_tree.is_node_can_be_executed(id)
+            if node.phrase == self.stm(self.workflow_instance_id)['phrase'] and can_be_executed:
+                current_nodes.append(node)
+        
+        assert len(current_nodes) == 1, "There should be only one node in the last phrase"
+        node = current_nodes[0]
+        self.callback.info(agent_id=self.workflow_instance_id, progress='Concluder conduct result', message=node.current_task_input)
+        print("*"*50)
+        print(node.original_task_input)
+
+        print("*"*50)
+        print(node.current_task_input)
+
+        if self.stm(self.workflow_instance_id)['special_task'] == "sort":
+            print(sorted(json_repair.loads(str(node.original_task_input))))
+        elif self.stm(self.workflow_instance_id)['special_task'] == "keyword_count":
+            print(node.pre_task_input)
+        elif self.stm(self.workflow_instance_id)['special_task'] == "set_intersection":
+            print(node.pre_task_input)
+        token_usage = self.stm(self.workflow_instance_id)['token_usage']
+        self.callback.info(agent_id=self.workflow_instance_id, progress='Token usage', message=token_usage)
+        return {'Final output': node.current_task_input, 'token_usage': token_usage}
+    
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/generater.py b/omagent_core/advanced_components/workflow/general_got/agent/generater/generater.py
new file mode 100644
index 0000000000000000000000000000000000000000..292a4d41d20549f52028321c70200e3141b25528
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/generater.py
@@ -0,0 +1,213 @@
+import json
+import re
+from pathlib import Path
+from typing import List, Tuple, Any
+
+from pydantic import Field
+from tenacity import (
+    retry,
+    retry_if_exception_message,
+    stop_after_attempt,
+    stop_after_delay,
+)
+
+from omagent_core.utils.env import EnvVar
+from omagent_core.utils.registry import registry
+from omagent_core.utils.logger import logging
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.advanced_components.workflow.general_got.schemas.got_structure import TaskTree, TaskNode
+import json_repair
+from omagent_core.advanced_components.workflow.general_got.agent.utils import sort_parse_refine_answer, keyword_count_parse_refine_answer, set_intersection_parse_refine_answer
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskGenerater(BaseLLMBackend, BaseWorker):
+    """
+    A task generator class that handles task generation, aggregation and refinement using LLM.
+    """
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def next_process(self, process: str, *args, **kwargs):
+        """
+        Determines the next process in the workflow sequence.
+        
+        Args:
+            process: Current process name
+            
+        Returns:
+            str: Next process name according to the process map
+        """
+        process_map = {
+            'conduct': "aggregate",
+            'aggregate': "refine",
+            'refine': "aggregate"
+        }
+        return process_map[process]
+
+    def _run(self, *args, **kwargs):
+        """
+        Main execution method for task generation.
+        Handles the task generation, aggregation and refinement processes using LLM.
+        
+        Returns:
+            dict: Contains the updated task tree structure
+        """
+        query = self.stm(self.workflow_instance_id)['query']
+        task_tree = self.stm(self.workflow_instance_id)['task_tree']
+        try:
+            process = self.stm(self.workflow_instance_id)['process']
+            process = self.next_process(process)
+        except:
+            process = "conduct"
+
+        # Get current executable nodes at the current phrase
+        current_nodes = []
+        for id in task_tree.leaves:
+            node = task_tree.get_node(id)
+            can_be_executed = task_tree.is_node_can_be_executed(id)
+            if node.phrase == self.stm(self.workflow_instance_id)['phrase'] and can_be_executed:
+                current_nodes.append(node)
+        
+        special_task = self.stm(self.workflow_instance_id)['special_task']
+        # Define prompts based on special task and process
+        sys_prompt = "sys_prompt.prompt"
+        user_prompt = "{}_{}_user_prompt.prompt".format(special_task, process)
+        self.prompts = [
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("{}".format(sys_prompt)), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("{}".format(user_prompt)), role="user"
+            ),
+        ]
+
+        if process == 'conduct':
+            # Handle the conduct process 
+            self.callback.info(agent_id=self.workflow_instance_id, progress='Num Branches Response', message=self.num_branches_response)
+            for node in current_nodes:
+                for i in range(self.num_branches_response):
+                    # Special handling for set_intersection task
+                    inputs = json_repair.loads(query)
+                    if special_task == "set_intersection":
+                        set1, set2 = inputs['set1'], inputs['set2']
+                        input = """Input Set 1: {}
+Input Set 2: {}""".format(set1, str(node.current_task_input))
+                    else:
+                        input = str(node.current_task_input)
+                    
+                    chat_complete_res = self.simple_infer(input=input)
+                    self.callback.info(agent_id=self.workflow_instance_id, progress='Generater conduct result', message=chat_complete_res)
+                    current_res = json_repair.loads(chat_complete_res['choices'][0]['message']['content'])
+                    new_node = node.copy_as_dict(pre_task_input = node.current_task_input, current_task_input=current_res, phrase=self.stm(self.workflow_instance_id)['phrase'] + 1)
+                    task_tree.add_node(new_node, [node.id])
+                    self.callback.info(agent_id=self.workflow_instance_id, progress='Add conducted new node', message=current_res)
+                node.executed = True
+
+        elif process == 'aggregate':
+            # Handle the aggregate process - combine results from multiple nodes
+            self.callback.info(agent_id=self.workflow_instance_id, progress='Num Branches Response for Aggregater', message=self.num_branches_response_for_aggregater)
+            
+            # Process nodes in pairs
+            for i in range(0, len(current_nodes), 2):
+                if i + 1 >= len(current_nodes):
+                    # Handle odd number of nodes
+                    node1 = current_nodes[i]
+                    new_node = node1.copy_as_dict(phrase=self.stm(self.workflow_instance_id)['phrase'] + 1)
+                    task_tree.add_node(new_node, [node.id for node in [node1]])
+                    self.callback.info(agent_id=self.workflow_instance_id, progress='Add aggregated same node', message="")
+                    node1.executed = True
+                else:
+                    # Aggregate pair of nodes
+                    node1 = current_nodes[i]
+                    node2 = current_nodes[i+1]
+                    for j in range(self.num_branches_response_for_aggregater):
+                        # Special handling for sort task
+                        if special_task == "sort":
+                            chat_complete_res = self.simple_infer(length1=str(len(json_repair.loads(str(node1.current_task_input)))), length2=str(len(json_repair.loads(str(node2.current_task_input)))), total_length=str(len(json_repair.loads(str(node1.current_task_input)) + json_repair.loads(str(node2.current_task_input)))), input1=str(node1.current_task_input), input2=str(node2.current_task_input))
+                        else:
+                            chat_complete_res = self.simple_infer(input1=str(node1.current_task_input), input2=str(node2.current_task_input))
+                        
+                        self.callback.info(agent_id=self.workflow_instance_id, progress='Aggregrate conduct result', message=chat_complete_res)
+                        current_res = json_repair.loads(chat_complete_res['choices'][0]['message']['content'])
+                        
+                        new_node = node1.copy_as_dict(original_task_input=node1.original_task_input + node2.original_task_input, 
+                                                    pre_task_input = {"task1":node1.current_task_input, "task2": node2.current_task_input}, 
+                                                    current_task_input=current_res, 
+                                                    phrase=self.stm(self.workflow_instance_id)['phrase'] + 1)
+                        task_tree.add_node(new_node, [node.id for node in [node1, node2]])
+                        self.callback.info(agent_id=self.workflow_instance_id, progress='Add aggregated new node', message=current_res)
+                    
+                    for node in [node1, node2]:
+                        node.executed = True
+
+        elif process == 'refine':
+            # Handle the refine process - improve existing results
+            self.callback.info(agent_id=self.workflow_instance_id, progress='Num Branches Response for Refiner', message=self.num_branches_response_for_refiner)
+
+            # Select appropriate parsing method based on special task
+            if special_task in ["sort", "set_intersection", "keyword_count"]:
+                self.parse_method = "{}_parse_refine_answer".format(special_task)
+            else:
+                self.parse_method = ""
+
+            # Get the corresponding parsing function
+            self.parsing_function = None
+            if self.parse_method == "sort_parse_refine_answer":
+                self.parsing_function = sort_parse_refine_answer
+            elif self.parse_method == "keyword_count_parse_refine_answer":
+                self.parsing_function = keyword_count_parse_refine_answer
+            elif self.parse_method == "set_intersection_parse_refine_answer":
+                self.parsing_function = set_intersection_parse_refine_answer
+            else:
+                raise ValueError("Invalid parse method: {}".format(self.parse_method))
+            
+            # Process each node for refinement
+            for node in current_nodes:
+                for i in range(self.num_branches_response_for_refiner):
+                    # Special handling for keyword_count task
+                    if special_task == "keyword_count":
+                        if "task1" in node.pre_task_input and "task2" in node.pre_task_input:
+                            combined_input = """Dictionary 1:
+{input1}
+Dictionary 2:
+{input2}
+""".format(input1=str(node.pre_task_input["task1"]), input2=str(node.pre_task_input["task2"]))
+                            chat_complete_res = self.simple_infer(input=combined_input, incorrectly_input=json_repair.loads(str(node.current_task_input)))
+                        else:
+                            chat_complete_res =  { 'choices': [{'message': {'content': str(node.current_task_input)}}]}
+                    elif special_task == "set_intersection":
+                        # Dummy process for set_intersection
+                        chat_complete_res =  { 'choices': [{'message': {'content': str(node.current_task_input)}}]}
+                    else:
+                        chat_complete_res = self.simple_infer(input=str(json_repair.loads(str(node.original_task_input))), incorrectly_input=json_repair.loads(str(node.current_task_input)))
+                    
+                    self.callback.info(agent_id=self.workflow_instance_id, progress='Generater conduct result', message=chat_complete_res)
+                    current_res = self.parsing_function(chat_complete_res['choices'][0]['message']['content'])
+                    
+                    new_node = node.copy_as_dict(pre_task_input = node.current_task_input, current_task_input=current_res, phrase=self.stm(self.workflow_instance_id)['phrase'] + 1)
+                    task_tree.add_node(new_node, [node.id])
+                    self.callback.info(agent_id=self.workflow_instance_id, progress='Add refined new node', message=current_res)
+                node.executed = True
+
+        else:
+            raise ValueError("Invalid process: {}".format(self.process))
+        
+        # Update workflow state
+        self.stm(self.workflow_instance_id)['phrase'] = self.stm(self.workflow_instance_id)['phrase'] + 1
+        self.stm(self.workflow_instance_id)['task_tree'] = task_tree
+        self.stm(self.workflow_instance_id)['process'] = process
+        return {'got_structure': task_tree.model_dump()}
+
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_aggregate_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_aggregate_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..bf69b36b58735dd556ae0c74732a0d5ef8f69451
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_aggregate_user_prompt.prompt
@@ -0,0 +1,17 @@
+<Instruction> Combine the following 2 dictionaries, each containing the frequency of countries in a text, into a single dictionary.
+Simply add the frequencies together for each country and if a country is not present in one of the dictionaries, add it to the final dictionary with the frequency from the other dictionary.
+Only output the final merged dictionary without any additional text or thoughts! </Instruction>
+
+<Approach>
+To combine the 2 dictionaries into single one, follow these steps:
+1. Create a new dictionary to store the combined frequencies.
+2. Iterate through the keys of the first dictionary and add the frequency of each country to the new dictionary.
+3. Iterate through the keys of the second dictionary and add the frequency of each country to the new dictionary and if it is already present, add the frequency to the existing value.
+</Approach>
+
+Combine the following 2 dictionaries into a single dictionary:
+{{input1}}
+
+{{input2}}
+
+Combined Output:
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_conduct_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_conduct_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..fc11408e87b60642f3469f7da7397ceb9560d3f3
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_conduct_user_prompt.prompt
@@ -0,0 +1,54 @@
+<Instruction> Count the frequency of how many times each country is explicitly named in the input text. Output only the frequency of each country that appears at least once in the following json format; make sure to keep the same spelling and output no additional text:
+{
+    "country1": frequency1,
+    "country2": frequency2,
+    ...
+}
+</Instruction>
+
+<Examples>
+Input:
+Alexandra boarded the first flight of her grand journey, starting from Canada. With a globe-trotting itinerary in hand, she was filled with excitement. Her first stop was Mexico, where she marveled at the Mayan ruins. From there, she explored the rainforests of Brazil and danced the tango in Argentina.
+Output: 
+{
+    "Canada": 1,
+    "Mexico": 1,
+    "Brazil": 1,
+    "Argentina": 1    
+}
+
+Input:
+The adventure led him to the peaks of Peru where he trekked to see the mysteries of Machu Picchu. He then headed to Chile to gaze at the vastness of the Atacama Desert. A quick detour to Uruguay and Paraguay allowed him to experience the vibrancy of the local cultures before returning back to Canada through Peru, Brazil and Mexico.
+Output: 
+{
+    "Peru": 2,
+    "Chile": 1,
+    "Uruguay": 1,
+    "Paraguay": 1,
+    "Canada": 1,
+    "Brazil": 1,
+    "Mexico": 1
+}
+
+Input:
+Journeying westward, she admired the art in Italy and sipped coffee in France. The music of Spain and the history of Greece deepened her love for Europe. The Nordic beauty of Norway, Sweden, Finland, and Denmark took her breath away. She danced in Ireland, explored castles in Scotland, and marveled at the architecture in Germany and Russia. Italy, Norway, Sweden and Germany will always stay her favourite destinations to visit.
+Output:
+{
+    "Italy": 2,
+    "France": 1,
+    "Spain": 1,
+    "Greece": 1,
+    "Norway": 2,
+    "Sweden": 2,
+    "Finland": 1,
+    "Denmark": 1,
+    "Ireland": 1,
+    "Scotland": 1,
+    "Germany": 2,
+    "Russia": 1
+}
+</Examples>
+
+Input:
+{{input}}
+Output:
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_refine_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_refine_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..579351fd42d6a97cee2e99997948aa2fb34959fe
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/keyword_count_refine_user_prompt.prompt
@@ -0,0 +1,46 @@
+<Instruction> The following 2 dictionaries were combined into the third dictionary below. 
+However, some mistakes occured and the third dictionary is incorrect. Please fix the third dictionary so that it contains the correct frequencies for each country.
+The correct frequencies are the sum of the frequencies from the first 2 dictionaries. If a country is not present in one of the dictionaries, add it to the final dictionary with the frequency from the other dictionary.
+
+<Example>
+Dictionary 1:
+{
+    "Peru": 2,
+    "Chile": 1,
+    "Uruguay": 1,
+    "Paraguay": 1
+}
+Dictionary 2:
+{
+    "Peru": 1,
+    "Argentina": 1,
+    "Canada": 1,
+    "Chile": 3,
+    "Germany": 2
+}
+Incorrectly Combined Dictionary:
+{
+    "Peru": 3,
+    "Chile": 2,
+    "Uruguay": 1,
+    "Paraguay": 1,
+    "Argentina": 1,
+    "Chile": 3,
+    "Germany": 2
+}
+Output:
+{
+    "Peru": 3,
+    "Chile": 4,
+    "Uruguay": 1,
+    "Paraguay": 1,
+    "Argentina": 1,
+    "Canada": 1,
+    "Germany": 2
+}
+</Example>
+
+{{input}}
+Incorrectly Combined Dictionary:
+{{incorrectly_input}}
+Output:
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_aggregate_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_aggregate_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..691622124ce0d60cacf98f634b888e6b80ddffa7
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_aggregate_user_prompt.prompt
@@ -0,0 +1,5 @@
+<Instruction> Merge the following 2 lists into one list by appending the second list to the first list.
+Only output the final list without any additional text or thoughts! </Instruction>
+
+List 1: {{input1}}
+List 2: {{input2}}
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_conduct_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_conduct_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..2589ae91265ce20424d323e592e01607b45dd2ca
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_conduct_user_prompt.prompt
@@ -0,0 +1,17 @@
+<Instruction> Find the intersection of two sets of numbers. Output only the set of numbers that are present in both sets, no additional text. </Instruction>
+
+<Examples>
+Input Set 1: [13, 16, 30, 6, 21, 7, 31, 15, 11, 1, 24, 10, 9, 3, 20, 8]
+Input Set 2: [25, 24, 10, 4, 27, 0, 14, 12, 8, 2, 29, 20, 17, 19, 26, 23]
+Output: [24, 10, 20, 8]
+
+Input Set 1: [26, 40, 42, 57, 15, 31, 5, 32, 11, 4, 24, 28, 51, 54, 12, 22, 33, 35, 7, 13, 2, 59, 8, 23, 43, 16, 29, 55, 25, 63, 21, 18]
+Input Set 2: [16, 60, 36, 48, 0, 15, 5, 19, 46, 24, 1, 6, 61, 10, 38, 53, 58, 9, 44, 14, 35, 63, 52, 20, 27, 17, 39, 47, 34, 56, 40, 59]
+Output: [40, 15, 5, 24, 35, 59, 16, 63]
+
+Input Set 1: [115, 61, 35, 103, 90, 117, 86, 44, 63, 45, 40, 30, 74, 33, 31, 1, 118, 48, 38, 0, 119, 51, 64, 78, 15, 121, 89, 101, 79, 69, 120, 29, 58, 50, 116, 11, 60, 12, 39, 95, 23, 2, 109, 84, 7, 43, 99, 98, 52, 70, 75, 102, 57, 19, 94, 36, 114, 88, 71, 56, 83, 6, 96, 107]
+Input Set 2: [13, 35, 20, 96, 34, 18, 47, 127, 126, 9, 21, 16, 77, 22, 111, 122, 85, 73, 42, 105, 123, 15, 33, 59, 67, 57, 104, 8, 30, 89, 76, 12, 65, 84, 32, 40, 7, 100, 108, 50, 14, 28, 24, 53, 90, 17, 91, 81, 124, 63, 5, 46, 125, 93, 49, 66, 117, 37, 115, 113, 2, 106, 41, 72]
+Output: [115, 35, 90, 117, 63, 40, 30, 33, 15, 89, 50, 12, 2, 84, 7, 57, 96]
+</Examples>
+
+{{input}}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_refine_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/set_intersection_refine_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_aggregate_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_aggregate_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..2d8843c1e2a4422f9e8ea6a83bdc2fa1305fe05f
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_aggregate_user_prompt.prompt
@@ -0,0 +1,16 @@
+<Instruction> Merge the following 2 sorted lists of length {{length1}} and {{length2}} respectively, into one sorted list of length {{total_length}} using a merge sort style approach.
+Only output the final merged list without any additional text or thoughts!:</Instruction>
+
+<Approach>
+To merge the two lists in a merge-sort style approach, follow these steps:
+1. Compare the first element of both lists.
+2. Append the smaller element to the merged list and move to the next element in the list from which the smaller element came.
+3. Repeat steps 1 and 2 until one of the lists is empty.
+4. Append the remaining elements of the non-empty list to the merged list.
+</Approach>
+
+Merge the following two lists into one sorted list:
+1: {{input1}}
+2: {{input2}}
+
+Merged list:
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_conduct_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_conduct_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..6232951f6c406b0e227eb4cc82a5319c1be5d6dc
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_conduct_user_prompt.prompt
@@ -0,0 +1,14 @@
+<Instruction> Sort the following list of numbers in ascending order. Output only the sorted list of numbers, no additional text. </Instruction>
+
+<Examples>
+Input: [5, 1, 0, 1, 2, 0, 4, 8, 1, 9, 5, 1, 3, 3, 9, 7]
+Output: [0, 0, 1, 1, 1, 1, 2, 3, 3, 4, 5, 5, 7, 8, 9, 9]
+
+Input: [3, 7, 0, 2, 8, 1, 2, 2, 2, 4, 7, 8, 5, 5, 3, 9, 4, 3, 5, 6, 6, 4, 4, 5, 2, 0, 9, 3, 3, 9, 2, 1]
+Output: [0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 9]
+
+Input: [4, 4, 9, 7, 9, 7, 0, 0, 4, 9, 1, 7, 9, 5, 8, 7, 5, 6, 3, 8, 6, 7, 5, 8, 5, 0, 6, 3, 7, 0, 5, 3, 7, 5, 2, 4, 4, 9, 0, 7, 8, 2, 7, 7, 7, 2, 1, 3, 9, 9, 7, 9, 6, 6, 4, 5, 4, 2, 0, 8, 9, 0, 2, 2]
+Output: [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9]
+</Examples>
+
+Input: {{input}}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_refine_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_refine_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..cf58ebe8be7ac3d2cd25b9d85d428daaf57ee5b1
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/generater/sort_refine_user_prompt.prompt
@@ -0,0 +1,28 @@
+<Instruction> The following two lists represent an unsorted list of numbers and a sorted variant of that list. The sorted variant is not correct. Fix the sorted variant so that it is correct.
+Make sure that the output list is sorted in ascending order, has the same number of elements as the input list ({{length}}), and contains the same elements as the input list. </Instruction>
+
+<Approach>
+To fix the incorrectly sorted list follow these steps:
+1. For each number from 0 to 9, compare the frequency of that number in the incorrectly sorted list to the frequency of that number in the input list.
+2. Iterate through the incorrectly sorted list and add or remove numbers as needed to make the frequency of each number in the incorrectly sorted list match the frequency of that number in the input list.
+</Approach>
+
+<Examples>
+Input: [3, 7, 0, 2, 8, 1, 2, 2, 2, 4, 7, 8, 5, 5, 3, 9]
+Incorrectly Sorted: [0, 0, 0, 0, 0, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 7, 7, 8, 8, 9, 9, 9, 9]
+Reason: The incorrectly sorted list contains four extra 0s, two extra 4s and three extra 9s and is missing two 2s.
+Output: [0, 1, 2, 2, 2, 2, 3, 3, 4, 5, 5, 7, 7, 8, 8, 9]
+
+Input: [6, 4, 5, 7, 5, 6, 9, 7, 6, 9, 4, 6, 9, 8, 1, 9, 2, 4, 9, 0, 7, 6, 5, 6, 6, 2, 8, 3, 9, 5, 6, 1]
+Incorrectly Sorted: [0, 1, 1, 2, 2, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 9, 9]
+Reason: The incorrectly sorted list contains two extra 4s and is missing two 6s and one 9.
+Output: [0, 1, 1, 2, 2, 3, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 8, 8, 9, 9, 9, 9, 9, 9]
+
+Input: [4, 4, 9, 7, 9, 7, 0, 0, 4, 9, 1, 7, 9, 5, 8, 7, 5, 6, 3, 8, 6, 7, 5, 8, 5, 0, 6, 3, 7, 0, 5, 3, 7, 5, 2, 4, 4, 9, 0, 7, 8, 2, 7, 7, 7, 2, 1, 3, 9, 9, 7, 9, 6, 6, 4, 5, 4, 2, 0, 8, 9, 0, 2, 2]
+Incorrectly Sorted: [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9]
+Reason: The incorrectly sorted list contains one extra 8 and is missing two 2s, one 3, three 4s, two 5s, one 6, six 7s and one 9.
+Output: [0, 0, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9]
+</Examples>
+
+Input: {{input}}
+Incorrectly Sorted: {{incorrectly_input}}
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/sys_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/generater/user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/generater/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/keep_best_n/keep_best_n.py b/omagent_core/advanced_components/workflow/general_got/agent/keep_best_n/keep_best_n.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6c3314729f96f043f5d554ec8984f2c5cf5956d
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/keep_best_n/keep_best_n.py
@@ -0,0 +1,92 @@
+import json
+import re
+from pathlib import Path
+from typing import List, Tuple, Any
+
+from pydantic import Field
+from tenacity import (
+    retry,
+    retry_if_exception_message,
+    stop_after_attempt,
+    stop_after_delay,
+)
+
+from omagent_core.utils.env import EnvVar
+from omagent_core.utils.registry import registry
+from omagent_core.utils.logger import logging
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.advanced_components.workflow.general_got.schemas.got_structure import TaskTree, TaskNode
+import json_repair
+
+
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class KeepBestN(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def _run(self, *args, **kwargs):
+        """
+        Keeps the best N nodes from the task tree based on their scores.
+        
+        This method:
+        1. Identifies nodes that can be executed in the current phrase
+        2. Groups nodes by their task type
+        3. Selects the top N nodes from each task group based on scores
+        4. Creates new nodes for the next phrase using the selected nodes
+        
+        TODO: Add evaluation using LLM
+        
+        Returns:
+            dict: Contains the updated task tree structure
+        """
+        query = self.stm(self.workflow_instance_id)['query']
+        task_tree = self.stm(self.workflow_instance_id)['task_tree']
+
+        
+        current_nodes = []
+        task_groups = {}
+        for id in task_tree.leaves:
+            node = task_tree.get_node(id)
+            can_be_executed = task_tree.is_node_can_be_executed(id)
+            if node.phrase == self.stm(self.workflow_instance_id)['phrase'] and can_be_executed:
+                current_nodes.append(node)
+                task_groups[node.task] = task_groups.get(node.task, []) + [node]
+        
+        assert all(
+            node.scored for node in current_nodes
+        ), "Not all thoughts have been scored"
+        
+        for key in task_groups.keys():
+            task_groups[key].sort(key=lambda x: x.score, reverse=self.higher_is_better)
+            predecessors = [node.id for node in task_groups[key]]
+            task_groups[key] = task_groups[key][:self.best_n]
+            new_node = task_groups[key][0].copy_as_dict(phrase=self.stm(self.workflow_instance_id)['phrase'] + 1)
+            if self.best_n == 1:
+                new_node['current_task_input'] = task_groups[key][0].current_task_input
+            else:
+                new_node['current_task_input'] = [node.current_task_input for node in task_groups[key]]
+            task_tree.add_node(new_node, predecessors)
+            self.callback.info(agent_id=self.workflow_instance_id, progress="Add best {} nodes".format(self.best_n), message=new_node['current_task_input'])
+
+        self.stm(self.workflow_instance_id)['task_tree'] = task_tree
+        self.stm(self.workflow_instance_id)['phrase'] = self.stm(self.workflow_instance_id)['phrase'] + 1
+
+        self.callback.info(agent_id=self.workflow_instance_id, progress="Select best n nodes finished", message=new_node['current_task_input'])
+        return {'got_structure': task_tree.model_dump()}
+
+
+
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/keep_best_n/sys_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/keep_best_n/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/keep_best_n/user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/keep_best_n/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/score/score.py b/omagent_core/advanced_components/workflow/general_got/agent/score/score.py
new file mode 100644
index 0000000000000000000000000000000000000000..7acf5a1be4ab5b572cda680fa0a26c8e68c6c6f7
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/score/score.py
@@ -0,0 +1,106 @@
+import json
+import re
+from pathlib import Path
+from typing import List, Tuple, Any
+
+from pydantic import Field
+from tenacity import (
+    retry,
+    retry_if_exception_message,
+    stop_after_attempt,
+    stop_after_delay,
+)
+
+from omagent_core.utils.env import EnvVar
+from omagent_core.utils.registry import registry
+from omagent_core.utils.logger import logging
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.advanced_components.workflow.general_got.schemas.got_structure import TaskTree, TaskNode
+import json_repair
+from omagent_core.advanced_components.workflow.general_got.agent.utils import sort_num_errors, keyword_count_num_errors, set_intersection_num_errors
+
+
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskScore(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+    eval_method: str = None
+
+    def _run(self, *args, **kwargs):
+        """Score task execution results
+        
+        Select appropriate scoring function based on special_task type.
+        Supported scoring methods:
+        - sort_num_errors: Scoring for sorting tasks
+        - keyword_count_num_errors: Scoring for keyword counting tasks
+        - set_intersection_num_errors: Scoring for set intersection tasks
+        """
+        query = self.stm(self.workflow_instance_id)['query']
+        task_tree = self.stm(self.workflow_instance_id)['task_tree']
+        special_task = self.stm(self.workflow_instance_id)['special_task']
+    
+        self.scoring_function = None
+        if special_task in ["sort", "set_intersection", "keyword_count"]:
+            self.eval_method = "{}_num_errors".format(special_task)
+        else:
+            self.eval_method = ""
+
+        # Select scoring function based on eval_method
+        if self.eval_method == "sort_num_errors":
+            self.scoring_function = sort_num_errors
+        elif self.eval_method == "keyword_count_num_errors":
+            self.scoring_function = keyword_count_num_errors
+        elif self.eval_method == "set_intersection_num_errors":
+            self.scoring_function = set_intersection_num_errors
+        else:
+            raise ValueError("Eval method must be provided".format(self.eval_method))
+
+        if self.scoring_function is not None:
+            self.callback.info(agent_id=self.workflow_instance_id, progress='Scoring', message="Using function {} to score nodes".format(self.scoring_function))
+        else:
+            raise ValueError("Eval method must be provided".format(self.eval_method))
+        
+        # Get executable leaf nodes
+        current_nodes = []
+        for id in task_tree.leaves:
+            node = task_tree.get_node(id)
+            can_be_executed = task_tree.is_node_can_be_executed(id)
+            if node.phrase == self.stm(self.workflow_instance_id)['phrase'] and can_be_executed:
+                current_nodes.append(node)
+        
+        # Score each node
+        for node in current_nodes:
+            if self.eval_method == "keyword_count_num_errors":
+                node_score = self.scoring_function(self.stm(self.workflow_instance_id)['all_possible_countries'], node.model_dump())
+            elif self.eval_method == "set_intersection_num_errors":
+                inputs = json_repair.loads(query)
+                set1, _ = inputs['set1'], inputs['set2']
+                node_score = self.scoring_function(set1, node.model_dump())
+            else:
+                node_score = self.scoring_function(node.model_dump())
+                
+            self.callback.info(agent_id=self.workflow_instance_id, progress="Original input: {}, Eval score for {}".format(node.original_task_input, node.current_task_input), message=node_score)
+            node.score = node_score
+            node.executed = True
+            node.scored = True
+            
+        self.stm(self.workflow_instance_id)['task_tree'] = task_tree
+        self.callback.info(agent_id=self.workflow_instance_id, progress="Score eval finished", message="")
+        return {'got_structure': task_tree.model_dump()}
+
+
+
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/score/sys_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/score/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/score/user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/score/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/splitter/keyword_count_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/splitter/keyword_count_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..a6e0b12c422873a9540a5415f0b6307eef615769
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/splitter/keyword_count_user_prompt.prompt
@@ -0,0 +1,24 @@
+<Instruction> Split the following input text into individual sentences.
+Output each sentence in the following format without any additional text or thoughts:
+{
+    "Sentence 1": "Some sentence text ...",
+    "Sentence 2": "Some sentence text ...",
+    "Sentence 3": "Some sentence text ...",
+    ...
+} </Instruction>
+
+<Example>
+Input:
+Journeying westward, she admired the art in Italy and sipped coffee in France. The music of Spain and the history of Greece deepened her love for Europe. The Nordic beauty of Norway, Sweden, Finland, and Denmark took her breath away. She danced in Ireland, explored castles in Scotland, and marveled at the architecture in Germany and Russia. Italy, Norway, Sweden and Germany will always stay her favourite destinations to visit.
+Output: 
+{
+    "Sentence 1": "Journeying westward, she admired the art in Italy and sipped coffee in France. ",
+    "Sentence 2": "The music of Spain and the history of Greece deepened her love for Europe. "
+    "Sentence 3": "The Nordic beauty of Norway, Sweden, Finland, and Denmark took her breath away.",
+    "Sentence 4": "She danced in Ireland, explored castles in Scotland, and marveled at the architecture in Germany and Russia.",
+    "Sentence 5": "Italy, Norway, Sweden and Germany will always stay her favourite destinations to visit."
+}
+</Example>
+
+Input:
+{{input}}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/splitter/set_intersection_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/splitter/set_intersection_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..6b091d1503fb98863cf6ecfdde45388bdc969556
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/splitter/set_intersection_user_prompt.prompt
@@ -0,0 +1,21 @@
+<Instruction> Split the following list of {{query_length}} numbers into {{num_chunks}} lists of {{chunk_size}} numbers each, the first list should contain the first {{chunk_size}} numbers and the second list the second {{chunk_size}} numbers, and so on.
+Only output the final {{num_chunks}} lists in the following format without any additional text or thoughts!:
+{
+    "List 1": [3, 4, 3, 5, 7, 8, 1, ...],
+    "List 2": [2, 9, 2, 4, 7, 1, 5, ...],
+    ...
+    "List {{num_chunks}}": [...],
+} </Instruction>
+
+<Example>
+Input: [3, 1, 9, 3, 7, 5, 5, 4, 8, 1, 5, 3, 3, 2, 3, 0, 9, 7, 2, 2, 4, 4, 8, 5, 0, 8, 7, 3, 3, 8, 7, 0, 9, 5, 1, 6, 7, 6, 8, 9, 0, 3, 0, 6, 3, 4, 8, 0, 6, 9, 8, 4, 1, 2, 9, 0, 4, 8, 8, 9, 9, 8, 5, 9]
+Output: 
+{
+    "List 1": [3, 1, 9, 3, 7, 5, 5, 4, 8, 1, 5, 3, 3, 2, 3, 0],
+    "List 2": [9, 7, 2, 2, 4, 4, 8, 5, 0, 8, 7, 3, 3, 8, 7, 0],
+    "List 3": [9, 5, 1, 6, 7, 6, 8, 9, 0, 3, 0, 6, 3, 4, 8, 0],
+    "List 4": [6, 9, 8, 4, 1, 2, 9, 0, 4, 8, 8, 9, 9, 8, 5, 9]
+}
+</Example>
+
+Input: {{input}}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/splitter/sort_user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/splitter/sort_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..6b091d1503fb98863cf6ecfdde45388bdc969556
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/splitter/sort_user_prompt.prompt
@@ -0,0 +1,21 @@
+<Instruction> Split the following list of {{query_length}} numbers into {{num_chunks}} lists of {{chunk_size}} numbers each, the first list should contain the first {{chunk_size}} numbers and the second list the second {{chunk_size}} numbers, and so on.
+Only output the final {{num_chunks}} lists in the following format without any additional text or thoughts!:
+{
+    "List 1": [3, 4, 3, 5, 7, 8, 1, ...],
+    "List 2": [2, 9, 2, 4, 7, 1, 5, ...],
+    ...
+    "List {{num_chunks}}": [...],
+} </Instruction>
+
+<Example>
+Input: [3, 1, 9, 3, 7, 5, 5, 4, 8, 1, 5, 3, 3, 2, 3, 0, 9, 7, 2, 2, 4, 4, 8, 5, 0, 8, 7, 3, 3, 8, 7, 0, 9, 5, 1, 6, 7, 6, 8, 9, 0, 3, 0, 6, 3, 4, 8, 0, 6, 9, 8, 4, 1, 2, 9, 0, 4, 8, 8, 9, 9, 8, 5, 9]
+Output: 
+{
+    "List 1": [3, 1, 9, 3, 7, 5, 5, 4, 8, 1, 5, 3, 3, 2, 3, 0],
+    "List 2": [9, 7, 2, 2, 4, 4, 8, 5, 0, 8, 7, 3, 3, 8, 7, 0],
+    "List 3": [9, 5, 1, 6, 7, 6, 8, 9, 0, 3, 0, 6, 3, 4, 8, 0],
+    "List 4": [6, 9, 8, 4, 1, 2, 9, 0, 4, 8, 8, 9, 9, 8, 5, 9]
+}
+</Example>
+
+Input: {{input}}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/splitter/splitter.py b/omagent_core/advanced_components/workflow/general_got/agent/splitter/splitter.py
new file mode 100644
index 0000000000000000000000000000000000000000..72a0a40917f5442959d346f3a5104e772b9e4496
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/splitter/splitter.py
@@ -0,0 +1,126 @@
+import json
+import re
+from pathlib import Path
+from typing import List, Tuple, Any
+import math
+from pydantic import Field
+from tenacity import (
+    retry,
+    retry_if_exception_message,
+    stop_after_attempt,
+    stop_after_delay,
+)
+
+from omagent_core.utils.env import EnvVar
+from omagent_core.utils.registry import registry
+from omagent_core.utils.logger import logging
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.advanced_components.workflow.general_got.schemas.got_structure import TaskTree, TaskNode
+import json_repair
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class TaskSplitter(BaseLLMBackend, BaseWorker):
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def _run(self, query: str, task: str, meta: dict, *args, **kwargs):
+        """A task splitter that breaks down complex tasks into multiple subtasks.
+        
+        Args:
+            query (str): The input query to be processed.
+            task (str): The type of task to be performed (e.g., 'sort', 'set_intersection', 'keyword_count').
+            meta (dict): Additional metadata required for task processing.
+            *args: Variable length argument list.
+            **kwargs: Arbitrary keyword arguments.
+
+        Returns:
+            dict: A dictionary containing the task tree structure with subtasks.
+                  Format: {'got_structure': <task_tree_model_dump>}
+
+        Raises:
+            ValueError: If required metadata is missing for specific task types.
+        """
+        
+        if self.special_task is not None:
+            self.prompts = [
+                PromptTemplate.from_file(
+                    CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+                ),
+                PromptTemplate.from_file(
+                    CURRENT_PATH.joinpath("{}_user_prompt.prompt".format(self.special_task)), role="user"
+                ),
+            ]
+        task_tree = TaskTree()
+        print(query)
+        if task is not None and task != "":
+            self.special_task = task
+        if meta is not None and meta != {}:
+            self.meta = meta
+        self.stm(self.workflow_instance_id)['query'] = query
+        self.stm(self.workflow_instance_id)['phrase'] = 0
+        self.stm(self.workflow_instance_id)['current_node_id'] = 0
+        self.stm(self.workflow_instance_id)['special_task'] = self.special_task
+        task_tree.add_node({"task": "split the list into two sublists", "original_task_input": self.stm(self.workflow_instance_id)['query'], "phrase": self.stm(self.workflow_instance_id)['phrase']})
+        self.callback.info(agent_id=self.workflow_instance_id, progress='Task Split', message=query)
+
+        if self.special_task == "sort":
+            # split the list into sublists by 8 elements
+            query_length = len(json_repair.loads(query))
+            if self.chunk_size is not None:
+                num_chunks = math.ceil(query_length / self.chunk_size)
+                self.callback.info(agent_id=self.workflow_instance_id, progress='Task Split', message="Input will be divided into {} sub tasks".format(num_chunks))
+            chat_complete_res = self.simple_infer(input=query, query_length=query_length, num_chunks=num_chunks, chunk_size=self.chunk_size)
+        elif self.special_task == "set_intersection":
+            self.callback.info(agent_id=self.workflow_instance_id, progress='Task Split', message="Chunk size is {}".format(self.chunk_size))
+            inputs = json_repair.loads(query)
+            set1, set2 = str(inputs['set1']), str(inputs['set2'])
+            query_length = len(json_repair.loads(set2))
+            if self.chunk_size is not None:
+                num_chunks = math.ceil(query_length / self.chunk_size)
+                self.callback.info(agent_id=self.workflow_instance_id, progress='Task Split', message="Input will be divided into {} sub tasks".format(num_chunks))
+            chat_complete_res = self.simple_infer(input=set2, query_length=query_length, num_chunks=num_chunks, chunk_size=self.chunk_size)
+        elif self.special_task == "keyword_count":
+            try:
+                self.stm(self.workflow_instance_id)['all_possible_countries'] = self.meta['all_possible_countries']
+            except Exception:
+                raise ValueError("all_possible_countries must be in meta information for keyword_count task. Please check the meta information.")
+            
+            chat_complete_res = self.simple_infer(input=query)
+        try:
+            subtasks = json_repair.loads(chat_complete_res['choices'][0]['message']['content'])
+            if self.special_task in ["sort", "set_intersection"] and len(subtasks.keys()) != num_chunks:
+                logging.warning("Expected {} lists in json, but found {}.".format(num_chunks, len(subtasks.keys())))
+            self.stm(self.workflow_instance_id)['phrase'] = self.stm(self.workflow_instance_id)['phrase'] + 1
+            task_tree.nodes[0].current_task_input = subtasks
+            for key, value in subtasks.items():
+                subtask = {
+                    "task": key, 
+                    "original_task_input": value, 
+                    "current_task_input": value,
+                    "phrase": self.stm(self.workflow_instance_id)['phrase'], 
+                    }
+                task_tree.add_node(subtask, [0])
+            
+        except Exception as e:
+            logging.error(f"Could not parse answer: {chat_complete_res}. Encountered exception: {e}")
+            subtasks = chat_complete_res["choices"][0]["message"]["content"]
+
+
+        self.callback.info(agent_id=self.workflow_instance_id, progress='Task Split', message=subtasks)
+        task_tree.get_node(0).executed = True
+        self.stm(self.workflow_instance_id)['task_tree'] = task_tree
+        return {'got_structure': task_tree.model_dump()}
+
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/splitter/sys_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/splitter/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/splitter/user_prompt.prompt b/omagent_core/advanced_components/workflow/general_got/agent/splitter/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..6b091d1503fb98863cf6ecfdde45388bdc969556
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/splitter/user_prompt.prompt
@@ -0,0 +1,21 @@
+<Instruction> Split the following list of {{query_length}} numbers into {{num_chunks}} lists of {{chunk_size}} numbers each, the first list should contain the first {{chunk_size}} numbers and the second list the second {{chunk_size}} numbers, and so on.
+Only output the final {{num_chunks}} lists in the following format without any additional text or thoughts!:
+{
+    "List 1": [3, 4, 3, 5, 7, 8, 1, ...],
+    "List 2": [2, 9, 2, 4, 7, 1, 5, ...],
+    ...
+    "List {{num_chunks}}": [...],
+} </Instruction>
+
+<Example>
+Input: [3, 1, 9, 3, 7, 5, 5, 4, 8, 1, 5, 3, 3, 2, 3, 0, 9, 7, 2, 2, 4, 4, 8, 5, 0, 8, 7, 3, 3, 8, 7, 0, 9, 5, 1, 6, 7, 6, 8, 9, 0, 3, 0, 6, 3, 4, 8, 0, 6, 9, 8, 4, 1, 2, 9, 0, 4, 8, 8, 9, 9, 8, 5, 9]
+Output: 
+{
+    "List 1": [3, 1, 9, 3, 7, 5, 5, 4, 8, 1, 5, 3, 3, 2, 3, 0],
+    "List 2": [9, 7, 2, 2, 4, 4, 8, 5, 0, 8, 7, 3, 3, 8, 7, 0],
+    "List 3": [9, 5, 1, 6, 7, 6, 8, 9, 0, 3, 0, 6, 3, 4, 8, 0],
+    "List 4": [6, 9, 8, 4, 1, 2, 9, 0, 4, 8, 8, 9, 9, 8, 5, 9]
+}
+</Example>
+
+Input: {{input}}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/task_exit_monitor/task_exit_monitor.py b/omagent_core/advanced_components/workflow/general_got/agent/task_exit_monitor/task_exit_monitor.py
new file mode 100644
index 0000000000000000000000000000000000000000..f73e957c98a462a9a57d2d7bf3d964b15d513701
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/task_exit_monitor/task_exit_monitor.py
@@ -0,0 +1,35 @@
+import json
+import re
+from pathlib import Path
+from typing import List, Tuple, Any
+
+from pydantic import Field
+from tenacity import (
+    retry,
+    retry_if_exception_message,
+    stop_after_attempt,
+    stop_after_delay,
+)
+
+from omagent_core.memories.ltms.ltm import LTM
+from omagent_core.utils.env import EnvVar
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.tool_system.manager import ToolManager
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.advanced_components.workflow.general_got.schemas.got_structure import TaskTree, TaskNode
+import json_repair
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class GoTTaskExitMonitor(BaseWorker):
+
+    def _run(self, *args, **kwargs):
+        task_tree = self.stm(self.workflow_instance_id)['task_tree']
+        if len(task_tree.leaves) == 1 and self.stm(self.workflow_instance_id)['process'] == 'refine':
+            return {"exit_flag": True}
+        else:
+            return {"exit_flag": False}
diff --git a/omagent_core/advanced_components/workflow/general_got/agent/utils.py b/omagent_core/advanced_components/workflow/general_got/agent/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c17ea5a37885cd482712f4ce95e9b84a0251be03
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/agent/utils.py
@@ -0,0 +1,226 @@
+from typing import Dict, List, Set
+import json_repair
+import logging
+import json
+
+def sort_num_errors(node_dict: Dict) -> float:
+    """
+    Function to locally count the number of errors that serves as a score.
+
+    Args:
+        node_dict (Dict): Dictionary containing the original and current task input lists.
+            Expected keys:
+            - original_task_input: The input list to be sorted
+            - current_task_input: The current solution list
+
+    Returns:
+        float: Number of errors in the current solution. Returns 300 if parsing fails.
+    """
+
+    try:
+        unsorted_list = node_dict["original_task_input"]
+        if (
+            "original_task_input" in node_dict
+            and node_dict["original_task_input"] != ""
+            and node_dict["original_task_input"] is not None
+            and len(node_dict["original_task_input"]) < len(unsorted_list) - 5
+        ):
+            unsorted_list = node_dict["original_task_input"]
+        correct_list = sorted(json_repair.loads(str(node_dict["original_task_input"])))
+        current_list = json_repair.loads(str(node_dict["current_task_input"]))
+        num_errors = 0
+        for i in range(10):
+            num_errors += abs(
+                sum([1 for num in current_list if num == i])
+                - sum([1 for num in correct_list if num == i])
+            )
+        num_errors += sum(
+            [1 for num1, num2 in zip(current_list, current_list[1:]) if num1 > num2]
+        )
+        return num_errors
+    except:
+        return 300
+    
+def keyword_count_num_errors(all_possible_countries: List[str], node_dict: Dict) -> float:
+    """
+    Function to locally count the number of errors that serves as a score.
+
+    Args:
+        all_possible_countries (List[str]): List of keywords (countries) to count.
+        node_dict (Dict): Dictionary containing the original text and current counting results.
+            Expected keys:
+            - original_task_input: The input text to analyze
+            - current_task_input: The current frequency dictionary of keywords
+
+    Returns:
+        float: Number of counting errors. Returns 100 if parsing fails.
+
+    Raises:
+        ValueError: If original_task_input is missing or empty.
+    """
+
+    try:
+        
+        if (
+            "original_task_input" in node_dict
+            and (node_dict["original_task_input"] != "" or node_dict["current_task_input"] == "{}")
+        ):
+            text = node_dict["original_task_input"]
+            correct_freq_dict = dict()
+            for country in all_possible_countries:
+                # find number of times country appears in text
+                num_occurrences = text.count(country)
+                correct_freq_dict[country] = num_occurrences
+        else:
+            raise ValueError("Please provide ground truth")
+        try:
+            current_freq_dict = json.loads(node_dict["current_task_input"])
+        except:
+            current_freq_dict = node_dict["current_task_input"]
+        countries_not_in_current = set(correct_freq_dict.keys()) - set(
+            current_freq_dict.keys()
+        )
+        countries_not_in_correct = set(current_freq_dict.keys()) - set(
+            correct_freq_dict.keys()
+        )
+        # count the number of errors
+        num_errors = 0
+        for country in countries_not_in_current:
+            num_errors += abs(correct_freq_dict[country])
+        for country in countries_not_in_correct:
+            num_errors += abs(current_freq_dict[country])
+        for country in set(correct_freq_dict.keys()) & set(current_freq_dict.keys()):
+            num_errors += abs(correct_freq_dict[country] - current_freq_dict[country])
+        return num_errors
+    except:
+        return 100
+    
+def string_to_set(string: str) -> Set[int]:
+    """Convert a string representation of a list into a set of integers.
+
+    Args:
+        string (str): String representation of a list (e.g., "[1,2,3]").
+
+    Returns:
+        Set[int]: Set containing the integer elements.
+
+    Raises:
+        AssertionError: If the input string is not in proper list format.
+    """
+    assert string[0] == "[" and string[-1] == "]", "String is not a list. {}".format(string)
+    return {int(num) for num in string[1:-1].split(",")}
+
+def set_intersection_num_errors(set1, node_dict: Dict) -> float:
+    """
+    Function to locally count the number of errors that serves as a score.
+
+    Args:
+        set1: First set for intersection operation.
+        node_dict (Dict): Dictionary containing the second set and current solution.
+            Expected keys:
+            - pre_task_input: The second set for intersection
+            - current_task_input: The current intersection result
+
+    Returns:
+        float: Number of errors in the intersection result.
+    """
+
+    # try:
+    set1 = string_to_set(str(set1))
+    try:
+        set2 = string_to_set(str(node_dict["pre_task_input"]))
+    except:
+        set2 = [v for k, v in node_dict["pre_task_input"].items()]
+        set2 = set([item for sublist in set2 for item in sublist])
+
+    common = sorted(list(set1 & set2))
+    llm_solution = sorted(list(string_to_set(str(node_dict["current_task_input"]))))
+    num_errors = 0
+    common_idx = 0
+    llm_idx = 0
+    while common_idx < len(common) and llm_idx < len(llm_solution):
+        if common[common_idx] == llm_solution[llm_idx]:
+            common_idx += 1
+            llm_idx += 1
+        elif common[common_idx] < llm_solution[llm_idx]:
+            common_idx += 1
+            num_errors += 1
+        elif common[common_idx] > llm_solution[llm_idx]:
+            llm_idx += 1
+            num_errors += 1
+    num_errors += len(common) - common_idx + len(llm_solution) - llm_idx
+    return num_errors
+    # except:
+    #     return 1000
+    
+
+
+def sort_parse_refine_answer(text: str) -> List[Dict]:
+    """Parse and extract the sorted list from the language model's response.
+
+    Args:
+        text (str): Raw response text from the language model.
+
+    Returns:
+        List[Dict]: Parsed answer containing the sorted list.
+            Returns empty list if parsing fails.
+
+    Notes:
+        - Extracts lists enclosed in square brackets
+        - Uses the last valid answer if multiple answers are found
+        - Ignores lines containing "Input" or "Incorrectly"
+    """
+
+    answers = text.strip().split("\n")
+    answers = [
+        answer for answer in answers if "[" in answer and "]" in answer
+    ]
+    if any(["Output" in answer for answer in answers]):
+        # cut elements until last output is found
+        for answer in reversed(answers):
+            if "Output" in answer:
+                answers = answers[answers.index(answer) :]
+                break
+
+    answers = [
+        answer[answer.index("[") : answer.index("]") + 1]
+        for answer in answers if "Input" not in answer and "Incorrectly" not in answer
+    ]
+    if len(answers) == 0:
+        logging.warning(
+            f"Could not parse step answer: {text}. Returning empty list."
+        )
+        answer = "[]"
+    else:
+        if len(answers) > 1:
+            logging.warning(
+                f"Multiple answers found for step answer: {text}. Using the last one."
+            )
+        answer = answers[-1]
+    return answer
+
+def keyword_count_parse_refine_answer(text: str) -> Dict:
+    """Parse the keyword counting response from the language model.
+
+    Args:
+        text (str): Raw response text from the language model.
+
+    Returns:
+        Dict: Parsed frequency dictionary of keywords.
+    """
+
+    answer = json_repair.loads(text)
+    return answer
+
+def set_intersection_parse_refine_answer(text: str) -> Dict:
+    """Parse the set intersection response from the language model.
+
+    Args:
+        text (str): Raw response text from the language model.
+
+    Returns:
+        Dict: Parsed intersection result.
+    """
+
+    answer = json_repair.loads(text)
+    return answer
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/general_got/schemas/got_structure.py b/omagent_core/advanced_components/workflow/general_got/schemas/got_structure.py
new file mode 100644
index 0000000000000000000000000000000000000000..c75a28eb102d6be5768de004a860d440b8032281
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/schemas/got_structure.py
@@ -0,0 +1,282 @@
+from enum import Enum
+from typing import Dict, List, Optional, Any
+from pydantic import BaseModel, Field
+
+
+class TaskStatus(str, Enum):
+    WAITING = "waiting"
+    RUNNING = "running"
+    SUCCESS = "success"
+    FAILED = "failed"
+
+
+class TaskNode(BaseModel):
+    id: int
+    predecessor_ids: List[int] = []
+    successor_ids: List[int] = []
+    executed: bool = False
+    can_be_executed: bool = False
+    task: str
+    phrase: int = 0
+    pre_task_input: Any = None
+    original_task_input: Any = ""
+    current_task_input: Any = None
+    score: int = 0
+    scored: bool = False
+    
+
+    def copy_as_dict(self, **kwargs):
+        new_node = self.model_dump()
+        del new_node['id']
+        del new_node['predecessor_ids']
+        del new_node['successor_ids']
+        new_node['successor_ids'] = []
+        new_node['executed'] = False
+        new_node['score'] = 0
+        new_node['scored'] = False
+        
+
+        for key, value in kwargs.items():
+            try:    
+                new_node[key] = value
+            except:
+                raise ValueError(f"Key {key} not found in TaskNode")
+
+        return new_node
+
+
+
+
+class TaskTree(BaseModel):
+    nodes: Dict[int, TaskNode] = Field(default_factory=dict)
+    cursor: int = 0
+    next_id: int = 0
+    leaves: List[int] = []
+    roots: List[int] = []
+        
+
+    ######
+    def is_node_can_be_executed(self, node_id: int)  -> bool:
+        """
+        Checks if the node can be executed based on its predecessors.
+
+        :return: True if all predecessors have been executed, False otherwise.
+        :rtype: bool
+        """
+        
+        return all(self.nodes.get(predecessor_id).executed for predecessor_id in self.nodes.get(node_id).predecessor_ids)
+    def get_node_id(self, node: TaskNode) -> int:
+        """
+        Get the ID of a given TaskNode.
+
+        :param node: The TaskNode to find the ID for
+        :return: The ID of the node, or -1 if not found
+        :rtype: int
+        """
+        for id, n in self.nodes.items():
+            if n == node:
+                return id
+        return -1
+
+    def get_node(self, node_id: int) -> TaskNode:
+        """
+        Get a TaskNode by its ID.
+
+        :param node_id: The ID of the node to retrieve
+        :return: The TaskNode object
+        :rtype: TaskNode
+        """
+        return self.nodes.get(node_id)
+
+    def _add_predecessor(self, node_id: int, predecessor_id: int):
+        """
+        Add a preceding node and update the relationships.
+
+        :param node_id: ID of the target node
+        :param predecessor_id: ID of the predecessor node
+        """
+        self.nodes.get(node_id).predecessor_ids.append(predecessor_id)
+        self.nodes.get(predecessor_id).successor_ids.append(node_id)
+    
+    def _add_successor(self, node_id: int, successor_id: int):
+        """
+        Add a successor relationship between two nodes.
+
+        :param node_id: ID of the target node
+        :param successor_id: ID of the successor node
+        """
+        self.nodes.get(node_id).successor_ids.append(successor_id)
+        self.nodes.get(successor_id).predecessor_ids.append(node_id)
+    
+    def get_predecessors(self, node_id: int) -> List[TaskNode]:
+        """
+        Get all predecessor nodes for a given node ID.
+
+        :param node_id: The ID of the node
+        :return: List of predecessor TaskNodes
+        :rtype: List[TaskNode]
+        """
+        if len(self.nodes.get(node_id).predecessor_ids) > 0:
+            return [node for node in self.nodes.values() if node.id in self.nodes.get(node_id).predecessor_ids]
+        return []
+
+    def get_successors(self, node_id: int) -> List[TaskNode]:
+        """
+        Get all successor nodes for a given node ID.
+
+        :param node_id: The ID of the node
+        :return: List of successor TaskNodes
+        :rtype: List[TaskNode]
+        """
+        if len(self.nodes.get(node_id).successor_ids) > 0:
+            return [node for node in self.nodes.values() if node.id in self.nodes.get(node_id).successor_ids]
+        return []
+    
+    def get_can_be_executed_nodes(self) -> List[TaskNode]:
+        """
+        Get all nodes that are ready to be executed.
+
+        :return: List of executable TaskNodes
+        :rtype: List[TaskNode]
+        """
+        return [node for node in self.nodes.values() if node.can_be_executed]
+    
+    def get_all_nodes(self) -> List[TaskNode]:
+        """
+        Get all nodes in the task tree.
+
+        :return: List of all TaskNodes
+        :rtype: List[TaskNode]
+        """
+        return list(self.nodes.values())
+    
+    def get_all_nodes_ids(self) -> List[int]:
+        """
+        Get IDs of all nodes in the task tree.
+
+        :return: List of all node IDs
+        :rtype: List[int]
+        """
+        return [node.id for node in self.nodes.values()]
+    
+    def add_node(self, task: dict, predecessor_ids: Optional[int] = []) -> TaskNode:
+        """Add a single node to the tree"""
+        node = TaskNode(
+            id=self.next_id,
+            predecessor_ids=predecessor_ids,
+            **task
+        )
+
+        self.nodes[node.id] = node
+        self.next_id += 1
+        if len(self.roots) == 0:
+            self.roots = [node.id]
+            assert (
+                len(node.predecessor_ids) == 0
+            ), "First operation should have no predecessors"
+            if len(node.successor_ids) == 0:
+                self.leaves = [node.id]
+            else:
+                raise ValueError("Successors should only be added to after nodes are added")
+        else:
+            if len(predecessor_ids) == 0:
+                self.roots.append(node.id)
+            node_predecessor_ids = predecessor_ids.copy()
+            for predecessor_id in node_predecessor_ids:
+                if predecessor_id in self.leaves:
+                    self.leaves.remove(predecessor_id)
+                self.nodes.get(predecessor_id).successor_ids.append(node.id)
+            self.leaves.append(node.id) 
+         
+        return node
+    
+    def append_node_to_all_leaves(self, task: dict) -> TaskNode:
+        """
+        Appends an operation to all leaves in the graph and updates the relationships.
+
+        :param operation: The operation to append.
+        :type operation: Operation
+        """
+        leaves = self.leaves
+        # node = TaskNode(
+        #     id=self.next_id,
+        #     predecessor_ids=leaves,
+        #     **task
+        # )
+        # self.nodes[node.id] = node
+        # self.next_id += 1
+        assert len(leaves) > 0, "Leaves should not be empty"
+        self.add_node(task, leaves)
+        # if len(self.roots) == 0:
+        #     self.add_node(task, leaves)
+        #     self.roots = [node.id]
+        #     self.leaves = [node.id]
+        # else:
+        #     self.add_node(task, leaves)
+        
+        return
+    
+if __name__ == "__main__":
+    import json
+    # Test code
+    tree = TaskTree()
+    
+    # 添加根任务
+    root = tree.add_node({"task": "search hangzhou weather tomorrow and save the result into a file"})
+    id = tree.get_node_id(root)
+    # 添加子任务
+    subtasks = [
+        {"task": "search hangzhou weather tomorrow"},
+        {"task": "analyze the weather data"},
+        {"task": "save the result into a file"}
+    ]
+
+    for task in subtasks:
+        tree.add_node(task, [id])
+
+    print(tree.nodes)
+    print(tree.roots)
+    print(tree.leaves)
+    print(tree.get_node_id(root))   
+
+
+    node1 = tree.nodes[1]
+    node2 = tree.nodes[2]
+    node3 = tree.nodes[3]
+
+    print(node1.predecessor_ids)
+    print(node2.predecessor_ids)
+    print(node3.predecessor_ids)
+
+
+
+    print(tree.get_node_id(node1))
+    print(tree.get_node_id(node2))
+    print(tree.get_node_id(node3))
+
+    print(tree.is_node_can_be_executed(node1.id))
+
+    print("############")
+    for node in tree.get_all_nodes():
+        print(node.id, tree.get_predecessors(node.id))
+
+    print("############")
+    for node in tree.get_all_nodes():
+        print(node.id, tree.get_successors(node.id))
+
+    print(tree.get_can_be_executed_nodes())
+    print(tree.get_all_nodes_ids())
+    print(tree.get_all_nodes())
+
+    task4 = {"task": "search hangzhou weather tomorrow"}
+    tree.append_node_to_all_leaves(task4)
+    print(tree.get_all_nodes_ids())
+    print(tree.get_all_nodes()) 
+    print(tree.leaves)
+    print(tree.roots)
+
+    for id in tree.leaves:
+        node = tree.get_node(id)
+
+
+
diff --git a/omagent_core/advanced_components/workflow/general_got/workflow.py b/omagent_core/advanced_components/workflow/general_got/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..ddadf6610ca05deed27f69f32187627a2953f50a
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/general_got/workflow.py
@@ -0,0 +1,65 @@
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.advanced_components.workflow.general_got.agent.splitter.splitter import TaskSplitter
+from omagent_core.advanced_components.workflow.general_got.agent.generater.generater import TaskGenerater
+from omagent_core.advanced_components.workflow.general_got.agent.score.score import TaskScore
+from omagent_core.advanced_components.workflow.general_got.agent.keep_best_n.keep_best_n import KeepBestN
+from omagent_core.advanced_components.workflow.general_got.agent.concluder.concluder import TaskConcluder
+from omagent_core.advanced_components.workflow.general_got.agent.task_exit_monitor.task_exit_monitor import GoTTaskExitMonitor
+from omagent_core.engine.workflow.task.do_while_task import DnCLoopTask
+
+
+
+class GoTWorkflow(ConductorWorkflow):
+    def __init__(self):
+        super().__init__(name='got_sort_workflow')
+        
+    def set_input(self, query: str, task: str, meta: dict):
+        self.query = query
+        self.task = task
+        self.meta = meta
+        self._configure_tasks()
+        self._configure_workflow()
+
+    def _configure_tasks(self):
+        self.splitter_task = simple_task(
+            task_def_name=TaskSplitter,
+            task_reference_name='task_splitter',
+            inputs={'query': self.query, "task": self.task, "meta": self.meta}
+        )
+
+        self.generater_task = simple_task(
+            task_def_name=TaskGenerater,
+            task_reference_name='task_generater'
+        )
+        self.score_task = simple_task(
+            task_def_name=TaskScore,
+            task_reference_name='task_score'
+        )
+
+        self.keep_best_n_task = simple_task(
+            task_def_name=KeepBestN,
+            task_reference_name='task_keep_best_n'
+        )
+
+
+        self.got_task_exit_monitor = simple_task(
+            task_def_name=GoTTaskExitMonitor,
+            task_reference_name='got_task_exit_monitor'
+        )
+
+
+        self.got_loop_task = DnCLoopTask(
+            task_ref_name='got_loop_task',
+            tasks=[self.generater_task, self.score_task, self.keep_best_n_task],
+            post_loop_exit=[self.got_task_exit_monitor]
+        )
+
+        self.concluder_task = simple_task(
+            task_def_name=TaskConcluder,
+            task_reference_name='task_concluder'
+        )
+
+    def _configure_workflow(self):
+        self >> self.splitter_task >> self.got_loop_task >> self.concluder_task
+
diff --git a/omagent_core/advanced_components/workflow/pot/README.md b/omagent_core/advanced_components/workflow/pot/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..bc75858c748064026770a9c584c5f970806fc5c7
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/pot/README.md
@@ -0,0 +1,35 @@
+# Program-of-Thought (PoT) Operator
+Program-of-Thought (PoT) is a workflow operator that solves math word problems by generating and executing Python code. It consists of two main components:
+
+1. A PoT Executor that uses an LLM to generate Python code implementing the solution steps, safely executes the code in an isolated environment, and extracts the numerical answer.
+
+2. A Choice Extractor that processes multiple choice questions by analyzing the generated answer against provided options using an LLM.
+
+You can refer to the examples in the `examples/PoT` directory to understand how to use this operator.
+
+# Inputs, Outputs and Configs
+
+## Inputs:
+The inputs that the Program-of-Thought (PoT) operator requires are as follows:
+| Name     | Type | Required | Description |
+| -------- | ----- | ----- | ---- |
+| query | str | true | The math word problem text to solve |
+| examples | str | false | Few-shot examples to guide code generation. If provided, uses a specialized few-shot prompt template |
+| options | str | false | Multiple choice options. If provided, triggers the Choice Extractor to analyze the answer against these options |
+
+## Outputs:
+The outputs that the Program-of-Thought (PoT) operator returns are as follows:
+| Name     | Type | Description |
+| -------- | ----- | ---- |
+| last_output | Union[float,str] | For regular problems: numerical answer as float. For multiple choice: selected option as string |
+| completion_tokens | int | Cumulative number of tokens in LLM completions |
+| prompt_tokens | int | Cumulative number of tokens in LLM prompts |
+
+## Configs:
+The config of the Program-of-Thought (PoT) operator is as follows, you can simply copy and paste the following config into your project as a pot_workflow.yml file.
+```yml
+- name: PoTExecutor
+  llm: ${sub| gpt}
+- name: ChoiceExtractor
+  llm: ${sub| gpt}
+```
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/pot/__init__.py b/omagent_core/advanced_components/workflow/pot/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/pot/agent/choice_extractor/choice_extractor.py b/omagent_core/advanced_components/workflow/pot/agent/choice_extractor/choice_extractor.py
new file mode 100644
index 0000000000000000000000000000000000000000..7310a1081d740d58498a49f14a6d09677e82424c
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/pot/agent/choice_extractor/choice_extractor.py
@@ -0,0 +1,92 @@
+from pathlib import Path
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from pydantic import Field
+from typing import List
+from omagent_core.utils.logger import logging
+
+
+# Get absolute path to the directory containing this file
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class ChoiceExtractor(BaseWorker, BaseLLMBackend):
+    """Choice extractor that processes multiple choice questions using LLM.
+    
+    This processor analyzes multiple choice questions and extracts the most likely answer:
+    1. Takes a multiple choice question and its options as input
+    2. Uses LLM to analyze the question and provided answer choices
+    3. Returns the selected answer choice with confidence score
+    4. Tracks token usage for monitoring purposes
+    
+    Attributes:
+        llm (OpenaiGPTLLM): The OpenAI GPT model used for answer analysis
+        prompts (List[PromptTemplate]): List of prompt templates used for LLM interaction.
+            Defaults to using the basic user prompt template.
+    """
+    llm: OpenaiGPTLLM
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def _run(self, query: str, prediction: str = None, completion_tokens: int = 0, prompt_tokens: int = 0, examples: str = None, options: str = None, *args, **kwargs):
+        """Processes a multiple choice question and extracts the most likely answer.
+        
+        This method coordinates the answer extraction process:
+        1. Validates input parameters
+        2. If no options provided, returns existing prediction
+        3. Otherwise uses LLM to analyze question and options
+        4. Tracks and accumulates token usage
+        5. Returns selected answer with usage metrics
+        
+        Args:
+            query (str): The multiple choice question text
+            prediction (str, optional): Existing prediction to return if no options
+            completion_tokens (int): Running count of completion tokens used
+            prompt_tokens (int): Running count of prompt tokens used  
+            examples (str, optional): Few-shot examples to guide answer selection
+            options (list, optional): List of possible answer choices
+            *args: Variable length argument list
+            **kwargs: Arbitrary keyword arguments
+            
+        Returns:
+            dict: A dictionary containing:
+                - 'last_output': The selected answer choice
+                - 'completion_tokens': Updated completion token count
+                - 'prompt_tokens': Updated prompt token count
+                
+        Note:
+            - Handles both direct return of predictions and LLM-based analysis
+            - Accumulates token counts across multiple LLM calls
+            - Logs extracted choices and usage metrics for monitoring
+        """
+        if query == '' or query is None:
+            answer =  {'last_output': None, 'completion_tokens': 0, 'prompt_tokens': 0}
+        
+        if options is None:
+            answer = {'last_output': prediction, 'completion_tokens': completion_tokens, 'prompt_tokens': prompt_tokens}
+        else:
+            chat_complete_res = self.simple_infer(question=query, options=options, prediction=prediction)
+
+            # Extract selected answer choice from LLM response
+            result = chat_complete_res["choices"][0]["message"]["content"]
+
+            logging.info('extracted choice: {}'.format(result))
+            logging.info(chat_complete_res['usage'])
+
+            # Accumulate token usage metrics
+            completion_tokens += chat_complete_res['usage']['completion_tokens']
+            prompt_tokens += chat_complete_res['usage']['prompt_tokens']
+
+            answer = {'last_output': result, 'completion_tokens': completion_tokens, 'prompt_tokens': prompt_tokens}
+
+        self.callback.send_answer(self.workflow_instance_id, msg=answer, filter_special_symbols=False)
+        return answer
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/pot/agent/choice_extractor/user_prompt.prompt b/omagent_core/advanced_components/workflow/pot/agent/choice_extractor/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..05ded83618921d12f885e79689a74eab09cab814
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/pot/agent/choice_extractor/user_prompt.prompt
@@ -0,0 +1,37 @@
+
+Find the closest options based on the question and prediction, only return the option letter.
+
+Question: A company produces 420 units of a particular computer component every month, at a production cost to the company of $110 per component, and sells all of the components by the end of each month. What is the minimum selling price per component that will guarantee that the yearly profit (revenue from sales minus production costs) will be at least $626,400 ?
+Options: ['A)226', 'B)230', 'C)240', 'D)260', 'E)280']
+Prediction: 234.28571428571428
+Closest Option: B
+
+Question: In how many ways can the letters of the word "PROBLEC" be rearranged to make 7 letter words such that none of the letters repeat?
+Options: ['A)2!', 'B)3!', 'C)7!', 'D)8!', 'E)9!']
+Prediction: 5040
+Closest Option: C
+
+Question: An exam is given in a certain class. The average (arithmetic mean) of the highest score and the lowest score is equal to x. If the average score for the entire class is equal to y and there are z students in the class, where z > 5, then in terms of x, y, and z, what is the average score for the class excluding the highest and lowest scorers?
+Options: ['A)(zy – 2x)/z', 'B)(zy – 2)/z', 'C)(zx – y)/(z – 2)', 'D)(zy – 2x)/(z -2)', 'E)(zy – x)/(z + 2)']
+Prediction: (-2*x + y*z)/(z - 2)
+Closest Option: D
+
+Question: Find the total no. of distinct bike no.'s that can beformed using 2 letters followed by 2 no.'s. How many letters need to be distinct?
+Options: ["A)74453", "B)64543", "C)74325", "D)65000", "E)97656"]
+Prediction = 67600
+Closest Option: D
+
+Question: A wire in the shape of rectangle of length 27 cm and breadth 17 cm is rebent to form a square. What will be the measure of each side?
+Options: ['A)9', 'B)11', 'C)22', 'D)25', 'E)31']
+Prediction = [-21.42428528562855, 21.42428528562855]
+Closest Option: C
+
+Question: A point on the edge of a fan blade that is rotating in a plane 10 centimeters from the center of the fan. What is the distance traveled, in centimeters, by this point after 30 seconds when the fan runs at the rate of 300 revolutions per minutes?
+Options: ['A)750pi', 'B)1500pi', 'C)1875pi', 'D)3000pi', 'E)7500pi']
+Prediction: 9424.77
+Closest Option: D
+
+Question: {{question}}
+Options: {{options}}
+Prediction: {{prediction}}
+Closest Option: 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/pot/agent/executor/PoTExecutor.py b/omagent_core/advanced_components/workflow/pot/agent/executor/PoTExecutor.py
new file mode 100644
index 0000000000000000000000000000000000000000..640d947b68357c7053e901e40311343d4fe490d0
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/pot/agent/executor/PoTExecutor.py
@@ -0,0 +1,328 @@
+from pathlib import Path
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from pydantic import Field
+from typing import List
+from omagent_core.utils.logger import logging
+import func_timeout
+import numpy as np
+import math
+import statistics
+from sympy import symbols
+import itertools
+
+# Get absolute path to the directory containing this file
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+
+@registry.register_worker()
+class PoTExecutor(BaseWorker, BaseLLMBackend):
+    """Program-of-Thought (PoT) executor that solves math word problems step by step.
+    
+    This executor takes a natural language math problem as input, uses a large language model (LLM)
+    to generate Python code that solves the problem, safely executes the generated code in an
+    isolated environment, and returns the numerical answer.
+    
+    The workflow consists of:
+    1. Receiving a math word problem text input
+    2. Using an LLM (OpenAI GPT) to generate Python code that solves the problem
+    3. Safely executing the generated code with timeouts and error handling
+    4. Processing and returning the numerical answer
+    
+    Attributes:
+        llm (OpenaiGPTLLM): The OpenAI GPT model used for code generation
+        prompts (List[PromptTemplate]): List of prompt templates used for LLM interaction.
+            Defaults to using the basic user prompt template.
+    """
+    llm: OpenaiGPTLLM
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def floatify_ans(self, ans):
+        """Attempts to convert an answer to float format when possible.
+        
+        This method handles various input types and tries to convert them to a float
+        representation while preserving the semantic meaning of the answer.
+        
+        Args:
+            ans: Answer in any format (dict, bool, list, tuple, etc.)
+            
+        Returns:
+            float or str: The converted answer as a float when possible, otherwise as a string.
+                Returns None if input is None or an empty list/tuple.
+                
+        Examples:
+            >>> floatify_ans({'result': 42})
+            42.0
+            >>> floatify_ans(True)
+            True
+            >>> floatify_ans([3.14])
+            3.14
+        """
+        try:
+            if ans is None:
+                return None
+            elif type(ans) == dict:
+                # For dictionaries, extract the first value
+                ans = list(ans.values())[0]
+            elif type(ans) == bool:
+                # Preserve boolean values without conversion
+                ans = ans
+            elif type(ans) in [list, tuple]:
+                if not ans:
+                    return None
+                else:
+                    # For sequences, try to convert first element to float
+                    try:
+                        ans = float(ans[0])
+                    except Exception:
+                        ans = str(ans[0])
+            else:
+                # For all other types, attempt float conversion
+                try:
+                    ans = float(ans)
+                except Exception:
+                    ans = str(ans)
+        except Exception as e:
+            logging.info(f"Error in floatify_ans: {e}")
+            return None
+        return ans
+
+    def extract_code_blocks(self, text, is_fewshot=False):
+        code_blocks = []
+        lines = text.split('\n')
+        if '```python' not in text:
+            if is_fewshot:
+                return text
+            
+            if 'def solver():' in text:
+                return text
+            
+            for line in lines:
+                if not line.strip():  # Skip empty lines or lines with only whitespace
+                    continue
+                if not line.startswith('    '):
+                    code_blocks.append('    ' + line)
+                else:
+                    code_blocks.append(line)
+            return '\n'.join(code_blocks)
+        
+        in_code_block = False
+        current_block = []
+        
+        for line in lines:
+            if line.strip().startswith('```'):
+                if in_code_block:
+                    # End of code block
+                    in_code_block = False
+                    if current_block:  # Only add non-empty blocks
+                        if current_block[0].startswith('    '):
+                            code_blocks.append('\n'.join(current_block))
+                            
+                        # Detect if code needs to be indented as a function
+                        elif 'def solver():' not in '\n'.join(current_block):
+                            # Add proper indentation for function body
+                            indented_block = []
+                            for code_line in current_block:
+                                if code_line.strip():  # Skip empty lines
+                                    if is_fewshot:
+                                        indented_block.append(code_line)
+                                    else:
+                                        indented_block.append('    ' + code_line)
+                                else:
+                                    indented_block.append(code_line)
+                            code_blocks.append('\n'.join(indented_block))
+                        else:
+                            code_blocks.append('\n'.join(current_block))
+                    current_block = []
+                else:
+                    # Start of code block
+                    in_code_block = True
+            elif in_code_block and not line.startswith('```'):
+                current_block.append(line)
+    
+        return '\n'.join(code_blocks)
+
+    def safe_execute(self, code_string: str, keys=None):
+        """Safely executes generated Python code with timeout protection.
+        
+        Provides a sandboxed environment for executing potentially unsafe code
+        with a timeout mechanism to prevent infinite loops or long-running code.
+        
+        Args:
+            code_string (str): Python code to execute
+            keys (List[str], optional): List of variable names to extract from locals()
+            
+        Returns:
+            Any: The execution result or None if execution fails/times out. 
+                If keys are provided, returns a list of values for those keys from locals().
+                
+        Note:
+            - Code execution is limited to 5 seconds
+            - Automatically handles markdown code block formatting
+            - Catches and logs all execution exceptions
+        """
+        def execute(x):
+            try:
+                exec(x)
+                locals_ = locals()
+                if keys is None:
+                    return locals_.get('ans', None)
+                else:
+                    return [locals_.get(k, None) for k in keys]
+            except Exception as e:
+                logging.info("\n--------------\nExecution error: error message {}, code_string:\n{}\n--------------".format(e, code_string))
+                return None
+        try:
+            # Execute with 5 second timeout
+            ans = func_timeout.func_timeout(5, execute, args=(code_string,))
+        except func_timeout.FunctionTimedOut:
+            ans = None
+
+        return ans
+    
+    def simplify_ans(self, ans, convert_to_str: bool = True):
+        """Simplifies and normalizes answer formats to consistent representations.
+        
+        Handles various numeric types including numpy arrays, sympy expressions,
+        and other mathematical objects, converting them to simple float or string format.
+        
+        Args:
+            ans: Answer in any format (numpy array, sympy expression, etc.)
+            convert_to_str (bool): Whether to convert the final result to string
+            
+        Returns:
+            Union[float, str, None]: Simplified answer in float or string format.
+                Returns None if input is falsy.
+                
+        Note:
+            - Rounds floating point numbers to 2 decimal places
+            - Handles special cases for numpy arrays and sympy expressions
+            - Preserves relational expressions as strings
+        """
+        if 'relational' in str(type(ans)):
+            # Preserve relational expressions as strings
+            return str(ans)
+        elif 'numpy' in str(type(ans)):
+            if ans.shape == ():
+                # Handle scalar numpy value
+                ans = round(float(ans), 2)
+            else:
+                # Handle array numpy value - take first element
+                ans = round(float(ans[0]), 2)
+            if convert_to_str:
+                return str(ans)
+            else:
+                return ans
+        elif not ans:
+            return None
+        else:
+            if type(ans) in [list, tuple]:
+                # Handle sympy expressions in lists/tuples
+                if 'sympy' in str(type(ans[0])):
+                    try:
+                        ans = [round(float(x), 2) for x in ans]
+                    except Exception:
+                        ans = [str(x) for x in ans]
+                if len(ans) == 1:
+                    ans = ans[0]
+            else:
+                # Handle single sympy expression
+                if 'sympy' in str(type(ans)):
+                    try:
+                        ans = round(float(ans), 2)
+                    except Exception:
+                        ans = str(ans)
+            if convert_to_str:
+                return str(ans)
+            else:
+                return ans
+
+    def _run(self, query: str, examples: str = None, options: str = None, *args, **kwargs):
+        """Processes a math word problem and returns the numerical answer.
+        
+        This is the main execution method that coordinates the entire solution process:
+        1. Validates input and selects appropriate prompt template
+        2. Generates Python code using LLM
+        3. Safely executes the code
+        4. Processes and returns the result
+        
+        Args:
+            query (str): The math word problem text to solve
+            examples (str, optional): Few-shot examples to guide code generation
+            options (list, optional): Additional processing options
+            *args: Variable length argument list
+            **kwargs: Arbitrary keyword arguments
+            
+        Returns:
+            dict: A dictionary containing:
+                - 'last_output': The numerical answer
+                - 'completion_tokens': Number of tokens in the completion
+                - 'prompt_tokens': Number of tokens in the prompt
+                - 'body': Response body
+            
+        Note:
+            - Handles both zero-shot and few-shot scenarios
+            - Automatically adds necessary imports for zero-shot cases
+            - Logs all major steps for debugging
+        """
+        logging.info("input query: {}".format(query))
+        if query == '' or query is None:
+            return {'last_output': None, 'total_tokens': 0}
+
+        # Select appropriate prompt template based on whether examples are provided
+        if examples is None:
+            # Use standard prompt for zero-shot
+            self.prompts = [
+                PromptTemplate.from_file(
+                    CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+                ),
+            ]
+            chat_complete_res = self.simple_infer(question=query)
+        else:
+            # Use few-shot prompt with examples
+            self.prompts = [
+                PromptTemplate.from_file(
+                    CURRENT_PATH.joinpath("user_prompt_fewshot.prompt"), role="user"
+                ),
+            ]
+            chat_complete_res = self.simple_infer(question=query, examples=examples)
+
+        # Extract generated code from LLM response
+        result = chat_complete_res["choices"][0]["message"]["content"]
+        print('---------------------\n')
+        print(result)
+       
+        result = self.extract_code_blocks(result, is_fewshot = examples != None)
+        
+        # For zero-shot cases, add imports and answer extraction
+        if examples is None:
+            if 'def solver():' in result:
+                result = result + '\nans = solver()'
+            else:
+                result = 'import math\nimport numpy as np\nimport statistics\ndef solver():\n' + result + '\nans = solver()'
+
+        logging.info('generated execution code:\n{}'.format(result))
+        
+        # Execute code safely and process result
+        ans = self.safe_execute(result)
+
+        if options is None:
+            prediction = self.floatify_ans(self.simplify_ans(ans, False))
+        else:
+            prediction = self.floatify_ans(self.simplify_ans(ans, True))
+        logging.info("Result of execution: {}".format(prediction))
+        logging.info(chat_complete_res['usage'])
+
+        completion_tokens = chat_complete_res['usage']['completion_tokens']
+        prompt_tokens = chat_complete_res['usage']['prompt_tokens']
+        
+        return {'last_output': prediction, 'completion_tokens': completion_tokens, 'prompt_tokens': prompt_tokens}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/pot/agent/executor/user_prompt.prompt b/omagent_core/advanced_components/workflow/pot/agent/executor/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..ffefaf7598ceec274860b4e7036f0289f201730e
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/pot/agent/executor/user_prompt.prompt
@@ -0,0 +1,10 @@
+
+import math
+import numpy as np
+import statistics
+
+# Question: {{question}}
+# Answer this question by implementing a solver() function, use for loop if necessary. return the answer in the end of function. Only generate excutable code, no other text.
+def solver():
+    # Let's write a Python program step by step, and then return the answer
+    # Firstly, we need define the following variable:
diff --git a/omagent_core/advanced_components/workflow/pot/agent/executor/user_prompt_fewshot.prompt b/omagent_core/advanced_components/workflow/pot/agent/executor/user_prompt_fewshot.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..b038c5b5d85bac9e99cbd5683caee47a176e4056
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/pot/agent/executor/user_prompt_fewshot.prompt
@@ -0,0 +1,6 @@
+Read the following questions and answer them with Python code, store the result as a 'ans' variable.
+Here are some examples:
+{{examples}}
+Now, answer the following question:
+Question: {{question}}
+# Python code, return ans
diff --git a/omagent_core/advanced_components/workflow/pot/workflow.py b/omagent_core/advanced_components/workflow/pot/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfd673851bbc38abd551651f3aa6d25d761d4ae1
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/pot/workflow.py
@@ -0,0 +1,57 @@
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.advanced_components.workflow.pot.agent.executor.PoTExecutor import PoTExecutor    
+from omagent_core.advanced_components.workflow.pot.agent.choice_extractor.choice_extractor import ChoiceExtractor
+from typing import List
+
+class PoTWorkflow(ConductorWorkflow):
+    """Program-of-Thought workflow that executes math word problem solving tasks.
+    
+    This workflow configures and executes the PoT executor to solve math problems
+    by generating and running Python code.
+    """
+    def __init__(self):
+        super().__init__(name='pot_workflow')
+        
+    def set_input(self, query: str, examples: str = None, options: str = None):
+        """Set input parameters and configure workflow.
+        
+        Args:
+            query: Math word problem text to solve
+            examples: Optional few-shot examples to guide code generation
+            options: Optional list of options to choose from
+        """
+        self.query = query
+        self.examples = examples
+        self.options = options
+        self._configure_tasks()
+        self._configure_workflow()
+
+    def _configure_tasks(self):
+        """Configure the PoT executor task with input parameters."""
+        self.pot_executor_task = simple_task(
+            task_def_name=PoTExecutor,  
+            task_reference_name='PoT_executor',
+            inputs={'query': self.query, 'examples': self.examples, 'options': self.options}
+        )
+        self.choice_extractor_task = simple_task(
+            task_def_name=ChoiceExtractor,  
+            task_reference_name='choice_extractor',
+            inputs={'query': self.query, 
+                    'examples': self.examples, 
+                    'options': self.options, 
+                    'prediction': self.pot_executor_task.output('last_output'),
+                    'completion_tokens': self.pot_executor_task.output('completion_tokens'),
+                    'prompt_tokens': self.pot_executor_task.output('prompt_tokens')}
+        )
+
+    def _configure_workflow(self):
+        """Configure workflow execution flow and output.
+        
+        Sets up task dependencies and captures the final numerical answer
+        from the executor's output.
+        """
+        self >> self.pot_executor_task >> self.choice_extractor_task
+        self.last_output = self.choice_extractor_task.output('last_output')
+        self.completion_tokens = self.choice_extractor_task.output('completion_tokens')
+        self.prompt_tokens = self.choice_extractor_task.output('prompt_tokens')
diff --git a/omagent_core/advanced_components/workflow/react/README.md b/omagent_core/advanced_components/workflow/react/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..19de8323a98948670342b715b3915e9963da85f6
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/README.md
@@ -0,0 +1,34 @@
+# React Operator
+React is a workflow operator that implements the basic ReAct (Reasoning + Acting) paradigm for question answering tasks. It combines thinking and action into a single component for streamlined processing.
+
+You can refer to the example in the `examples/react` directory to understand how to use this operator.
+
+# Inputs, Outputs and configs
+
+## Inputs:
+The inputs that the React operator requires are as follows:
+| Name     | Type | Required | Description |
+| -------- | ----- | ----- | ---- |
+| query | str | true | The question or task to be solved |
+| id | str | false | A unique identifier for the conversation. If not provided, a default empty string will be used |
+
+## Outputs:
+The outputs that the React operator returns are as follows:
+| Name     | Type | Description |
+| -------- | ----- | ---- |
+| output | str | The final answer or response |
+| query | str | The original question |
+| id | str | The conversation identifier |
+| token_usage | dict | Token usage statistics for the conversation |
+
+## Configs:
+The config of the React operator is as follows, you can simply copy and paste the following config into your project as a react_workflow.yml file.
+```yml
+- name: ThinkAction
+  llm: ${sub|text_res}
+  output_parser: 
+    name: StrParser
+- name: WikiSearch
+- name: ReactOutput
+```
+
diff --git a/omagent_core/advanced_components/workflow/react/__init__.py b/omagent_core/advanced_components/workflow/react/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cdabf31d819c7b2bf56c32ebfdbd237271eb3e94
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/__init__.py
@@ -0,0 +1,5 @@
+from .agent.think_action.think_action import ThinkAction
+from .agent.wiki_search.wiki_search import WikiSearch
+from .agent.react_output.react_output import ReactOutput
+
+__all__ = ['ThinkAction', 'WikiSearch', 'ReactOutput']
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react/agent/react_output/react_output.py b/omagent_core/advanced_components/workflow/react/agent/react_output/react_output.py
new file mode 100644
index 0000000000000000000000000000000000000000..c798cb514e81270f9af56c8fa69697a5270feef4
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/agent/react_output/react_output.py
@@ -0,0 +1,21 @@
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+
+@registry.register_worker()
+class ReactOutput(BaseWorker):
+    """Simple worker that passes through the final action output for React workflow"""
+    
+    def _run(self, action_output: str, workflow_id=None, *args, **kwargs):
+        """Simply return the action output with any necessary state"""
+        
+        state = self.stm(workflow_id)
+        query = state.get('query', '')
+        id = state.get('id', '')
+        token_usage = state.get('token_usage', {})
+        
+        return {
+            'output': action_output,
+            'query': query,
+            'id': id,
+            'token_usage': token_usage
+        } 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react/agent/think_action/sys_prompt.prompt b/omagent_core/advanced_components/workflow/react/agent/think_action/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..0519ecba6ea913e21689ec692e81e9e4973fbf73
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/agent/think_action/sys_prompt.prompt
@@ -0,0 +1 @@
+ 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react/agent/think_action/think_action.py b/omagent_core/advanced_components/workflow/react/agent/think_action/think_action.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc0bd51db0445de4b5d8ca8a02ffc64d03933fd1
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/agent/think_action/think_action.py
@@ -0,0 +1,135 @@
+from pathlib import Path
+from typing import List, Any
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.registry import registry
+from pydantic import Field
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+@registry.register_worker()
+class ThinkAction(BaseLLMBackend, BaseWorker):
+    """Combined Think and Action worker that implements ReAct (Reasoning and Acting) approach"""
+    
+    # debug_mode: bool = Field(default=False)
+    example: str = Field(default="")
+    max_steps: int = Field(default=8)
+
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), 
+                role="user"
+            ),
+        ]
+    )
+
+    def _run(self, query: str, id: str = "", next_step: str = "Thought", *args, **kwargs):
+        """Process the query using ReAct approach with combined Think and Action steps"""
+        # Get context from STM
+        state = self.stm(self.workflow_instance_id)
+        context = state.get('context', '')
+        
+        # Initialize token_usage
+        token_usage = state.get('token_usage', {
+            'prompt_tokens': 0,
+            'completion_tokens': 0,
+            'total_tokens': 0
+        })
+
+        # Initialize step_number for new conversation (empty context)
+        if not context:
+            state['step_number'] = 1
+        
+        # Get current step number
+        current_step = state.get('step_number', 1)
+
+        # Get model call parameters
+        message = self.prep_prompt([{"question": query}])
+        body = self.llm._msg2req(message[0])
+        self.stm(self.workflow_instance_id)["body"] = body
+
+        # Record input information
+        self.callback.info(
+            agent_id=self.workflow_instance_id, 
+            progress='ThinkAction', 
+            message=f'Step {current_step}'
+        )
+
+        # Build prompt
+        full_prompt = f"{context}\n{next_step} {current_step}:" if context else f"{self.example}\nQuestion: {query}\n{next_step} {current_step}:"
+        
+        try:
+            # First try to get thought and action in one call
+            response = self.simple_infer(
+                query=query, 
+                context=full_prompt,
+                stop=[f"\nObservation {current_step}:"]
+            )
+            output = response['choices'][0]['message']['content']
+            if "\nObservation" in output:
+                output = output.split("\nObservation")[0]
+            try:
+                # Try to separate thought and action
+                thought, action = output.strip().split(f"\nAction {current_step}: ")
+            except:
+                # If separation fails, make a second attempt
+                thought = output.strip().split('\n')[0]
+                action_response = self.simple_infer(
+                    query=query,
+                    context=f"{full_prompt}\n{thought}\nAction {current_step}:",
+                    stop=["\n"]
+                )
+                if "\n" in action_response['choices'][0]['message']['content']:
+                    action_response['choices'][0]['message']['content'] = action_response['choices'][0]['message']['content'].split("\n")[0]
+                action = action_response['choices'][0]['message']['content'].strip()
+            
+            # Update token usage
+            if 'usage' in response:
+                token_usage['prompt_tokens'] += response['usage']['prompt_tokens']
+                token_usage['completion_tokens'] += response['usage']['completion_tokens']
+                token_usage['total_tokens'] += response['usage']['total_tokens']
+            
+            # Combine output
+            output = f"{thought}\nAction {current_step}: {action}"
+            
+        except Exception as e:
+            self.callback.error(
+                agent_id=self.workflow_instance_id,
+                progress='ThinkAction Error',
+                message=f'Error: {str(e)}'
+            )
+            raise
+        
+        # Check if it's a Finish action
+        is_final = 'Finish[' in action
+        
+        # Record output information
+        self.callback.info(
+            agent_id=self.workflow_instance_id, 
+            progress='ThinkAction', 
+            message=f'Step {current_step}: {output}'
+        )
+        
+        # Update context and store in STM
+        new_context = f"{context}\n{next_step} {current_step}: {output}" if context else f"{self.example}\nQuestion: {query}\n{next_step} {current_step}: {output}"
+        state.update({
+            'context': new_context,
+            'query': query,
+            'id': id,
+            'token_usage': token_usage
+        })
+        
+        return {
+            'output': output,
+            'action': action,
+            'step_number': current_step,
+            'is_final': is_final,
+            'query': query,
+            'id': id,
+            'token_usage': token_usage,
+            'body': state.get('body', {}),
+            'max_steps': self.max_steps
+        }
+        
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react/agent/think_action/user_prompt.prompt b/omagent_core/advanced_components/workflow/react/agent/think_action/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..8202815b65a50096ed37593daf7d7d54c23f95a0
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/agent/think_action/user_prompt.prompt
@@ -0,0 +1,6 @@
+Solve a question answering task with interleaving Thought, Action, Observation steps. Thought can reason about the current situation, and Action can be three types: 
+(1) Search[entity], which searches the exact entity on Wikipedia and returns the first paragraph if it exists. If not, it will return some similar entities to search.
+(2) Lookup[keyword], which returns the next sentence containing keyword in the current passage.
+(3) Finish[answer], which returns the answer and finishes the task.
+
+{{context}} 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react/agent/wiki_search/wiki_search.py b/omagent_core/advanced_components/workflow/react/agent/wiki_search/wiki_search.py
new file mode 100644
index 0000000000000000000000000000000000000000..811203cf67501e7957c2b425ece85b07f9231c83
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/agent/wiki_search/wiki_search.py
@@ -0,0 +1,151 @@
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+from omagent_core.utils.logger import logging
+import wikipedia
+from pydantic import Field
+
+class WikipediaSearcher:
+    """Simple Wikipedia search implementation that mimics DocstoreExplorer"""
+    
+    def search(self, query: str) -> str:
+        """Search Wikipedia and return the page content"""
+        try:
+            page_content = wikipedia.page(query).content
+            return page_content
+        except wikipedia.PageError:
+            return f"Could not find [{query}]. Similar: {wikipedia.search(query)}"
+        except wikipedia.DisambiguationError:
+            return f"Could not find [{query}]. Similar: {wikipedia.search(query)}"
+
+@registry.register_worker()
+class WikiSearch(BaseWorker):
+    """Wiki Search worker for React workflow"""
+    
+    wiki_searcher: WikipediaSearcher = Field(default_factory=WikipediaSearcher)
+
+    def _run(self, action_output: str, *args, **kwargs):
+        """Execute search or lookup based on action output"""
+        try:
+            # Get context and other states from STM
+            state = self.stm(self.workflow_instance_id)
+            context = state.get('context', '')
+            step_number = self._get_step_number(context)
+            
+            if 'Search[' in action_output:
+                result = self._handle_search(action_output)
+            elif 'Lookup[' in action_output:
+                result = self._handle_lookup(action_output)
+            else:
+                result = action_output
+                
+            # Record observation result
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress='Observation',
+                message=f'Step {step_number}: {result}'
+            )
+                
+            # Update context (only save in STM, not return in response)
+            new_context = f"{context}\nObservation {step_number}: {result}"
+            
+            # Update states including context and step_number
+            state.update({
+                'context': new_context,
+                'step_number': step_number + 1  # Update step number at the end of a round
+            })
+            
+            # Return result with all necessary information
+            return {
+                'output': result
+            }
+            
+        except Exception as e:
+            logging.error(f"Error in WikiSearch: {str(e)}")
+            raise
+
+    def _handle_search(self, action_output: str) -> str:
+        """Handle Search action"""
+        search_term = self._extract_term('Search', action_output)
+        if not search_term:
+            return action_output
+        
+        try:
+            result = self.wiki_searcher.search(search_term)
+            if result:
+                result_text = result.strip('\n').strip().replace('\n', '')
+                
+                # Update state
+                self.stm(self.workflow_instance_id).update({
+                    'current_document': {'content': result},
+                    'lookup_str': '',
+                    'lookup_index': 0
+                })
+                
+                return result_text
+            else:
+                return f"No content found for '{search_term}'"
+        except Exception as e:
+            return f"Error occurred during search: {str(e)}"
+
+    def _handle_lookup(self, action_output: str) -> str:
+        """Handle Lookup action"""
+        lookup_term = self._extract_term('Lookup', action_output)
+        if not lookup_term:
+            return action_output
+            
+        try:
+            # Get state from state manager
+            state = self.stm(self.workflow_instance_id)
+            current_document = state.get('current_document', {})
+            lookup_str = state.get('lookup_str', '')
+            lookup_index = state.get('lookup_index', 0)
+
+            if not current_document:
+                return "No previous search results available. Please perform a search first."
+            
+            paragraphs = current_document['content'].split('\n\n')
+            
+            # Update lookup state
+            new_lookup_index = lookup_index
+            if lookup_term.lower() != lookup_str:
+                new_lookup_str = lookup_term.lower()
+                new_lookup_index = 0
+            else:
+                new_lookup_str = lookup_str
+                new_lookup_index += 1
+                
+            lookups = [p for p in paragraphs if lookup_term.lower() in p.lower()]
+            
+            if len(lookups) == 0:
+                result = "No Results"
+            elif new_lookup_index >= len(lookups):
+                result = "No More Results"
+            else:
+                result_prefix = f"(Result {new_lookup_index + 1}/{len(lookups)})"
+                result = f"{result_prefix} {lookups[new_lookup_index]}"
+                result = result.strip('\n').strip().replace('\n', '')
+            
+            # Update state
+            self.stm(self.workflow_instance_id).update({
+                'lookup_str': new_lookup_str,
+                'lookup_index': new_lookup_index
+            })
+            
+            return result
+            
+        except ValueError as ve:
+            return str(ve)
+        except Exception as e:
+            return f"Error occurred during lookup: {str(e)}"
+
+    def _extract_term(self, action_type: str, action_output: str) -> str:
+        """Extract term from action output"""
+        if f'{action_type}[' in action_output:
+            start = action_output.find(f'{action_type}[') + len(action_type) + 1
+            end = action_output.find(']', start)
+            return action_output[start:end].strip()
+        return ""
+
+    def _get_step_number(self, context: str) -> int:
+        """Get the current step number from STM"""
+        return self.stm(self.workflow_instance_id).get('step_number', 1) 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react/workflow.py b/omagent_core/advanced_components/workflow/react/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7fea50733ab4f211370af8e9273daf9776a3e43
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react/workflow.py
@@ -0,0 +1,65 @@
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.engine.workflow.task.do_while_task import DoWhileTask
+from .agent.think_action.think_action import ThinkAction
+from .agent.wiki_search.wiki_search import WikiSearch
+from .agent.react_output.react_output import ReactOutput
+
+
+class ReactWorkflow(ConductorWorkflow):
+    def __init__(self):
+        super().__init__(name='react_workflow')
+        
+    def set_input(self, query: str, id: str = ""):
+        self.query = query
+        self.id = id
+        self._configure_tasks()
+        self._configure_workflow()
+        
+    def _configure_tasks(self):
+        # Combined Think and Action task
+        self.think_action_task = simple_task(
+            task_def_name=ThinkAction,
+            task_reference_name='think_action',
+            inputs={
+                'query': self.query,
+                'id': self.id
+            }
+        )
+        
+        # Wiki Search task
+        self.wiki_search_task = simple_task(
+            task_def_name=WikiSearch,
+            task_reference_name='wiki_search',
+            inputs={
+                'action_output': self.think_action_task.output('action'),
+            }
+        )
+        
+        # Do-While loop with max_turns from config
+        self.loop_task = DoWhileTask(
+            task_ref_name='react_loop',
+            tasks=[self.think_action_task, self.wiki_search_task],
+            #inputs={'max_steps': self.max_steps},
+            termination_condition=f'''
+                if (($.think_action.is_final == true) || ($.think_action.step_number > $.think_action.max_steps)) {{
+                    false;  // Stop loop if it's a Finish action or exceeded max turns
+                }} else {{
+                    true;   // Continue loop otherwise
+                }}
+            '''
+        )
+        
+        # Output task
+        self.react_output_task = simple_task(
+            task_def_name=ReactOutput,
+            task_reference_name='react_output',
+            inputs={
+                'action_output': '${think_action.output}',
+                'workflow_id': '${workflow.workflowId}'
+            }
+        )
+        
+    def _configure_workflow(self):
+        # Configure workflow execution sequence
+        self >> self.loop_task >> self.react_output_task 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react_pro/README.md b/omagent_core/advanced_components/workflow/react_pro/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..112302688bcb8e70781a8e8ea00fd3f07e9646a7
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react_pro/README.md
@@ -0,0 +1,38 @@
+# React Pro Operator
+React Pro is an advanced workflow operator that implements the ReAct (Reasoning + Acting) paradigm for complex question answering tasks. It separates the thinking and action processes into distinct components for better control and observation.
+
+You can refer to the example in the `examples/react_pro` directory to understand how to use this operator.
+
+# Inputs, Outputs and configs
+
+## Inputs:
+The inputs that the React Pro operator requires are as follows:
+| Name     | Type | Required | Description |
+| -------- | ----- | ----- | ---- |
+| query | str | true | The question or task to be solved |
+| id | str | false | A unique identifier for the conversation. If not provided, a default empty string will be used |
+
+## Outputs:
+The outputs that the React Pro operator returns are as follows:
+| Name     | Type | Description |
+| -------- | ----- | ---- |
+| output | str | The final answer or response |
+| query | str | The original question |
+| id | str | The conversation identifier |
+| token_usage | dict | Token usage statistics for the conversation |
+
+## Configs:
+The config of the React Pro operator is as follows, you can simply copy and paste the following config into your project as a react_pro_workflow.yml file.
+```yml
+- name: Think
+  llm: ${sub|text_res}
+  output_parser: 
+    name: StrParser
+- name: Action
+  llm: ${sub|text_res}
+  output_parser: 
+    name: StrParser
+- name: WikiSearch
+- name: ReactOutput
+```
+
diff --git a/omagent_core/advanced_components/workflow/react_pro/__init__.py b/omagent_core/advanced_components/workflow/react_pro/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..61f8ed278556c2490788a438743254e3ad0b21f6
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react_pro/__init__.py
@@ -0,0 +1,6 @@
+from .agent.think.think import Think
+from .agent.action.action import Action
+from omagent_core.advanced_components.workflow.react.agent.wiki_search.wiki_search import WikiSearch
+from omagent_core.advanced_components.workflow.react.agent.react_output.react_output import ReactOutput
+
+__all__ = ['Think', 'Action', 'WikiSearch', 'ReactOutput']  
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react_pro/agent/action/action.py b/omagent_core/advanced_components/workflow/react_pro/agent/action/action.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d08029a2331e1ceac05af418feb506cdb3d6b96
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react_pro/agent/action/action.py
@@ -0,0 +1,94 @@
+from pathlib import Path
+from typing import List
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.registry import registry
+from pydantic import Field
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+@registry.register_worker()
+class Action(BaseLLMBackend, BaseWorker):
+    """Action worker that determines the next step in ReAct chain"""
+    
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), 
+                role="user"
+            ),
+        ]
+    )
+
+    def _run(self, *args, **kwargs):
+        """Process the query using ReAct approach"""
+        # Get context and other states from STM
+        state = self.stm(self.workflow_instance_id)
+        context = state.get('context', '')
+        query = state.get('query', '')
+        id = state.get('id', '')
+        token_usage = state.get('token_usage', {
+            'prompt_tokens': 0,
+            'completion_tokens': 0,
+            'total_tokens': 0
+        })
+        
+        # Get current step number
+        current_step = state.get('step_number', 1)
+        
+        # Record output information
+        self.callback.info(
+            agent_id=self.workflow_instance_id, 
+            progress='Action', 
+            message=f'Step {current_step}'
+        )
+        
+        # build prompt
+        full_prompt = f"{context}\nAction {current_step}:"
+        
+        # Add dynamic step number hint
+        full_prompt += f"\nNote: Only output Action {current_step}."
+        
+        # Get response
+        response = self.simple_infer(query=query, context=full_prompt)
+        
+        # Update token usage
+        if 'usage' in response:
+            token_usage['prompt_tokens'] += response['usage']['prompt_tokens']
+            token_usage['completion_tokens'] += response['usage']['completion_tokens']
+            token_usage['total_tokens'] += response['usage']['total_tokens']
+            # Update token_usage in STM
+            state.update({'token_usage': token_usage})
+        
+        # Process non-streaming response
+        output = response['choices'][0]['message']['content']
+        
+        # Check if it's a Finish action
+        is_final = 'Finish[' in output
+        
+        # Record output information
+        self.callback.info(
+            agent_id=self.workflow_instance_id, 
+            progress='Action', 
+            message=f'Action {current_step}: {output}'
+        )
+        
+        # Update context and store in STM
+        new_context = f"{context}\nAction {current_step}: {output}"
+        state.update({
+            'context': new_context,
+            'token_usage': token_usage
+        })
+        
+        return {
+            'output': output,
+            'step_number': current_step,
+            'is_final': is_final,
+            'query': query,
+            'id': id,
+            'token_usage': token_usage,
+            'body': state.get('body', {})
+        }
+    
+        
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react_pro/agent/action/sys_prompt.prompt b/omagent_core/advanced_components/workflow/react_pro/agent/action/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/react_pro/agent/action/user_prompt.prompt b/omagent_core/advanced_components/workflow/react_pro/agent/action/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..1ad874307acf028ddbd4378f1f418992bad363a3
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react_pro/agent/action/user_prompt.prompt
@@ -0,0 +1,11 @@
+Solve a question-answering task using alternating Thought, Action, and Observation steps:
+
+- Thought: Reason about the current situation.
+- Action: Can be one of these three types:
+  1. Search[entity]: Search for the specified entity online. If it exists, return its first paragraph; if not, return some similar entities for further search.
+  2. Lookup[keyword]: From the last successfully found paragraph through Search, return the next sentence containing the specified keyword.
+  3. Finish[answer]: Return the final answer and end the task.
+
+- You can take as many steps as needed.
+
+{{context}} 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react_pro/agent/think/sys_prompt.prompt b/omagent_core/advanced_components/workflow/react_pro/agent/think/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/react_pro/agent/think/think.py b/omagent_core/advanced_components/workflow/react_pro/agent/think/think.py
new file mode 100644
index 0000000000000000000000000000000000000000..786d1afbd94d80481138ec4b9c812b5d099b9571
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react_pro/agent/think/think.py
@@ -0,0 +1,99 @@
+from pathlib import Path
+from typing import List, Any
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.llms.prompt import PromptTemplate
+from omagent_core.utils.registry import registry
+from pydantic import Field
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+@registry.register_worker()
+class Think(BaseLLMBackend, BaseWorker):
+    """Think worker that implements ReAct (Reasoning and Acting) approach"""
+    
+    example: str = Field(default="")
+    max_steps: int = Field(default=8)
+    
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), 
+                role="user"
+            ),
+        ]
+    )
+
+    def _run(self, query: str, id: str = "", *args, **kwargs):
+        """Process the query using ReAct approach"""
+        # Get context from STM
+        state = self.stm(self.workflow_instance_id)
+        context = state.get('context', '')
+
+        # Initialize token_usage
+        token_usage = state.get('token_usage', {
+            'prompt_tokens': 0,
+            'completion_tokens': 0,
+            'total_tokens': 0
+        })
+
+        # Initialize step_number for new conversation (empty context)
+        if not context:
+            state['step_number'] = 1
+        
+        # Get current step number
+        current_step = state.get('step_number', 1)
+    
+        # Record input information
+        self.callback.info(
+            agent_id=self.workflow_instance_id, 
+            progress='Think', 
+            message=f'Step {current_step}'
+        )
+        
+        # Build prompt
+        full_prompt = f"{context}\nThought {current_step}:" if context else f"{self.example}\nQuestion: {query}\nThought {current_step}:"
+        
+        # Add dynamic step number hint
+        full_prompt += f"\nNote: Only output Thought {current_step}."
+        
+        # Get response
+        response = self.simple_infer(query=query, context=full_prompt)
+
+        # Get model call parameters
+        message = self.prep_prompt([{"question": query}])
+        body = self.llm._msg2req(message[0])
+        state["body"] = body
+        
+        # Process non-streaming response
+        output = response['choices'][0]['message']['content']
+        
+        # Update token usage
+        if 'usage' in response:
+            token_usage['prompt_tokens'] += response['usage']['prompt_tokens']
+            token_usage['completion_tokens'] += response['usage']['completion_tokens']
+            token_usage['total_tokens'] += response['usage']['total_tokens']
+            # Update token_usage in STM
+            state.update({'token_usage': token_usage})
+    
+        # Record output information
+        self.callback.info(
+            agent_id=self.workflow_instance_id, 
+            progress='Think', 
+            message=f'Thought {current_step}: {output}'
+        )
+        
+        # Update context and store in STM
+        new_context = f"{context}\nThought {current_step}: {output}" if context else f"{self.example}\nQuestion: {query}\nThought {current_step}: {output}"
+        state.update({
+            'context': new_context,
+            'id': id,
+            'query': query,
+            'step_number': current_step  # Update step number
+        })
+
+        return {
+            'response': output,
+            'step_number': current_step,
+            'max_steps': self.max_steps
+        } 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react_pro/agent/think/user_prompt.prompt b/omagent_core/advanced_components/workflow/react_pro/agent/think/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..1ad874307acf028ddbd4378f1f418992bad363a3
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react_pro/agent/think/user_prompt.prompt
@@ -0,0 +1,11 @@
+Solve a question-answering task using alternating Thought, Action, and Observation steps:
+
+- Thought: Reason about the current situation.
+- Action: Can be one of these three types:
+  1. Search[entity]: Search for the specified entity online. If it exists, return its first paragraph; if not, return some similar entities for further search.
+  2. Lookup[keyword]: From the last successfully found paragraph through Search, return the next sentence containing the specified keyword.
+  3. Finish[answer]: Return the final answer and end the task.
+
+- You can take as many steps as needed.
+
+{{context}} 
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/react_pro/workflow.py b/omagent_core/advanced_components/workflow/react_pro/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..123894d0c59e84e4db6c0840485c6c4f578e7359
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/react_pro/workflow.py
@@ -0,0 +1,70 @@
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.engine.workflow.task.do_while_task import DoWhileTask
+from .agent.action.action import Action
+from .agent.think.think import Think
+from omagent_core.advanced_components.workflow.react.agent.wiki_search.wiki_search import WikiSearch
+from omagent_core.advanced_components.workflow.react.agent.react_output.react_output import ReactOutput
+
+class ReactProWorkflow(ConductorWorkflow):
+    def __init__(self):
+        super().__init__(name='react_pro')
+        
+    def set_input(self, query: str, id: str = ""):
+        self.query = query
+        self.id = id
+        self._configure_tasks()
+        self._configure_workflow()
+        
+    def _configure_tasks(self):
+        # Think task
+        self.think_task = simple_task(
+            task_def_name=Think,                
+            task_reference_name='think',
+            inputs={
+                'query': self.query,
+                'id': self.id
+            }
+        )
+        
+        # Action task
+        self.action_task = simple_task(
+            task_def_name=Action,
+            task_reference_name='action'
+        )
+        
+        # Wiki Search task
+        self.wiki_search_task = simple_task(
+            task_def_name=WikiSearch,
+            task_reference_name='wiki_search',
+            inputs={
+                'action_output': self.action_task.output('output')
+            }
+        )
+        
+        # Do-While loop with max_turns from config
+        self.loop_task = DoWhileTask(
+            task_ref_name='react_pro_loop',
+            tasks=[self.think_task, self.action_task, self.wiki_search_task],
+            termination_condition=f'''
+                if (($.action.is_final == true) || ($.think.step_number > $.think.max_steps)) {{
+                    false;  // Stop loop if it's a Finish action or exceeded max turns
+                }} else {{
+                    true;   // Continue loop otherwise
+                }}
+            '''
+        )
+        
+        # Configure output task
+        self.react_output_task = simple_task(
+            task_def_name=ReactOutput,
+            task_reference_name='react_output',
+            inputs={
+                'action_output': '${action.output}',
+                'workflow_id': '${workflow.workflowId}'
+            }
+        )
+        
+    def _configure_workflow(self):
+        # Configure workflow execution sequence
+        self >> self.loop_task >> self.react_output_task
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/README.md b/omagent_core/advanced_components/workflow/self_consist_cot/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..195d52f143df8d843575e73252592b740d66f687
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/README.md
@@ -0,0 +1,110 @@
+# Self-Consistent Chain-of-Thought (CoT) Workflow
+
+A workflow component that implements self-consistent chain-of-thought reasoning for complex problem-solving tasks. This component generates multiple reasoning paths and aggregates them to produce a more reliable final answer.
+
+## Overview
+
+The Self-Consistent CoT workflow consists of three main stages:
+
+1. **CoT Reasoning**: Generates multiple reasoning paths for the given question
+2. **CoT Extract**: Extracts final answers from each reasoning path
+3. **CoT Conclusion**: Analyzes extracted answers to produce a final consensus answer
+
+## Inputs, Outputs and Configs
+
+### Inputs:
+The inputs that the Self-Consistent CoT workflow requires are as follows:
+| Name     | Type | Required | Description |
+| -------- | ----- | ----- | ---- |
+| user_question | str | true | The question or task to be solved |
+| path_num | int | false | Number of reasoning paths to generate (default: 5) |
+
+### Outputs:
+The outputs that the Self-Consistent CoT operator returns are as follows:
+| Name     | Type | Description |
+| -------- | ----- | ---- |
+| final_answer | str | The final concluded answer from COTConclusion |
+| question | str | The original input question |
+| prompt_tokens | int | Number of prompt tokens used |
+| completion_tokens | int | Number of completion tokens used |
+| body | str | The response body from COTConclusion |
+
+### Configs:
+The config of the Self-Consistent CoT workflow is as follows, you can simply copy and paste the following config into your project as a self_consist_cot_workflow.yml file.
+```yml
+- name: COTReasoning
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: COTExtract
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: COTConclusion
+  llm: ${sub|text_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+```
+
+## Components
+
+### COTReasoning
+
+Generates multiple independent reasoning paths for the given question. Each path shows step-by-step reasoning leading to an answer.
+
+### COTExtract
+
+Extracts final answers from each reasoning path, focusing on the conclusion rather than the intermediate steps.
+
+### COTConclusion
+
+Analyzes all extracted answers to determine the most consistent and reliable final answer.
+
+## Usage
+
+```python
+from omagent_core.advanced_components.workflow.self_consist_cot.workflow import SelfConsistentWorkflow
+
+# Initialize the workflow
+workflow = SelfConsistentWorkflow()
+
+# Set input parameters
+workflow.set_input(
+    user_question="Your question here",  # The question to be answered
+    path_num=5                          # Number of reasoning paths to generate
+)
+```
+
+## Example
+
+Here's a simple example using the GSM8K math dataset:
+
+```python
+# Question: "Janet's ducks lay 16 eggs per day. She eats three for breakfast every morning and 
+# bakes muffins for her friends every day with four. She sells the remainder at the farmers' 
+# market daily for $2 per fresh duck egg. How much money does she make per day at the farmers' market?"
+
+workflow = SelfConsistentWorkflow()
+workflow.set_input(user_question=question, path_num=5)
+```
+
+The workflow will:
+1. Generate 5 different reasoning paths
+2. Extract answers from each path
+3. Analyze the answers to produce a final consensus
+
+## Performance
+
+The self-consistent approach helps improve accuracy by:
+- Generating multiple independent reasoning attempts
+- Cross-validating answers across different paths
+- Reducing the impact of individual reasoning errors
+
+## Dependencies
+
+- OpenAI GPT models for reasoning
+- Redis for state management
+- OmAgent core components
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/__init__.py b/omagent_core/advanced_components/workflow/self_consist_cot/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_conclude/cot_conclude.py b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_conclude/cot_conclude.py
new file mode 100644
index 0000000000000000000000000000000000000000..ee7ff5f36b1305f906af5c21312cbad64481ceb6
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_conclude/cot_conclude.py
@@ -0,0 +1,56 @@
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
+from omagent_core.models.llms.schemas import Message, Content
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.utils.logger import logging
+from pathlib import Path
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from pydantic import Field
+from typing import List
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+@registry.register_worker()
+class COTConclusion(BaseLLMBackend, BaseWorker):
+    
+    """
+    COTConclusion is a worker class that leverages OpenAI's GPT model to conclude the reasoning process
+    based on previous interactions. It manages the inference workflow, collects token usage metrics,
+    and sends final answers back to the workflow instance. The class is registered in the worker registry
+    for use within the agent's operational framework.
+    """
+
+
+    llm: OpenaiGPTLLM
+
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+   
+    def _run(self,  final_answer:List[str],question:str, *args, **kwargs):
+        
+        """
+        Executes the reasoning conclusion process based on the final answers provided.
+
+        Args:
+            final_answer (List[str]): A list of final answers to conclude from.
+            question (str): The question that was posed to the reasoning process.
+            *args: Additional positional arguments.
+            **kwargs: Additional keyword arguments.
+
+        Returns:
+            dict: A dictionary containing the final answer, question, prompt tokens,
+                  completion tokens, and the body of the request.
+        """
+        reasoning_result = self.simple_infer(final_answer=str(final_answer))
+        prompt_tokens = sum(self.stm(self.workflow_instance_id)['prompt_token'])   + reasoning_result["usage"]["prompt_tokens"]
+        completion_tokens = sum(self.stm(self.workflow_instance_id)['completion_token']) + reasoning_result["usage"]["completion_tokens"]
+        reasoning_result = reasoning_result["choices"][0]["message"]["content"]
+        self.stm(self.workflow_instance_id)['final_answer'] = reasoning_result
+        body = self.stm(self.workflow_instance_id)["body"] 
+        return {'final_answer': reasoning_result,'question': question,'prompt_tokens': prompt_tokens, 'completion_tokens': completion_tokens,"body":body}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_conclude/user_prompt.prompt b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_conclude/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..620cc623e873f8daab22aacd77a6e5c4b47e6d24
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_conclude/user_prompt.prompt
@@ -0,0 +1,6 @@
+The following are different responses to a problem. Please analyze the answers, identify the one that appears most frequently, and provide it as the final answer. If there is a tie between multiple answers, choose any one of them.
+
+Responses:
+{{final_answer}}
+
+Please output only the final answer, without any explanation or additional text.
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_extract/cot_extract.py b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_extract/cot_extract.py
new file mode 100644
index 0000000000000000000000000000000000000000..9754b4b8a716813b52beb819c09afe36325def34
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_extract/cot_extract.py
@@ -0,0 +1,61 @@
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.models.llms.prompt import prompt
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
+from omagent_core.models.llms.schemas import Message, Content
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.utils.logger import logging
+import ast
+from typing import List
+from pathlib import Path
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from pydantic import Field
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+@registry.register_worker()
+class COTExtract(BaseLLMBackend, BaseWorker):
+
+
+    """ 
+    COTExtract class for handling extraction tasks using OpenaiGPTLLM.
+    It manages the prompts and interacts with the base worker architecture.
+    """
+
+    llm: OpenaiGPTLLM
+
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def _run(self,  reasoning_result:List[str], *args, **kwargs):
+
+        """
+        Executes the reasoning process based on the final answers and question provided.
+
+        Args:
+            reasoning_result (List[str]): A list of reasoning results to be processed.
+            *args: Additional positional arguments.
+            **kwargs: Additional keyword arguments.
+
+        Returns:
+            dict: A dictionary containing the final answer and prompt and completion tokens.
+        """
+        final_answer = []
+        prompt_tokens = []
+        complete_tokens = []
+        for item in reasoning_result:
+            reasoning_result = self.simple_infer(reasoning_step=item)
+            prompt_tokens.append(reasoning_result["usage"]["prompt_tokens"])
+            complete_tokens.append(reasoning_result["usage"]["completion_tokens"])
+            reasoning_result = reasoning_result["choices"][0]["message"]["content"]
+            final_answer.append(reasoning_result)
+        self.stm(self.workflow_instance_id)['prompt_token'].extend(prompt_tokens)
+        self.stm(self.workflow_instance_id)['completion_token'].extend(complete_tokens)
+        self.stm(self.workflow_instance_id)['reasoning_result'] = final_answer
+        self.callback.send_answer(self.workflow_instance_id, msg=",".join(final_answer))
+        return {'final_answer': final_answer}
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_extract/user_prompt.prompt b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_extract/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..920f08c332b6dd210fa3966c6ee6d3ee46a221cd
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_extract/user_prompt.prompt
@@ -0,0 +1,5 @@
+The following is one reasoning step with detailed explanations. Extract and provide only the concise final answers this step:
+
+Reasoning step: {{reasoning_step}}
+
+Please extract and provide the final answer for this reasoning step:
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_reasoning/cot_reasoning.py b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_reasoning/cot_reasoning.py
new file mode 100644
index 0000000000000000000000000000000000000000..2ad0eb0b3ea5c3cb22380ad39a7f60c88f861793
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_reasoning/cot_reasoning.py
@@ -0,0 +1,78 @@
+from itertools import combinations_with_replacement
+from omagent_core.models.llms.openai_gpt import OpenaiGPTLLM
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.base import BaseLLMBackend
+from omagent_core.models.llms.schemas import Message, Content
+from omagent_core.utils.logger import logging
+from pathlib import Path
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from pydantic import Field
+from typing import List
+
+from pydantic_core.core_schema import NoInfoWrapValidatorFunctionSchema
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+@registry.register_worker()
+class COTReasoning(BaseLLMBackend, BaseWorker):
+    
+    """
+    
+    COTReasoning class is responsible for reasoning using the OpenAI GPT model.
+    It manages prompts and invokes the model to generate reasoning paths based on user queries.
+    The _run method executes the inference process, collecting reasoning results and token usage.
+    
+    """
+
+
+    llm: OpenaiGPTLLM
+
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def _run(self, user_question: str, num_path: int, examples: str = None, *args, **kwargs):
+        
+        """
+            Executes the reasoning process using the OpenAI GPT model.
+
+        Args:
+            user_question (str): The question posed by the user for reasoning.
+            num_path (int): The number of reasoning paths to generate.
+            examples (str, optional): Examples to guide the reasoning process.
+
+        Returns:
+            dict: A dictionary containing the reasoning results.
+        """
+        prompt_token = []
+        complete_token = []
+        if self.llm.model_id in ["gpt4o","gpt4o-mini","gpt3.5-turbo"]:
+            reasoning_result = self.infer(input_list=[{"question": user_question, "examples": examples}], n=num_path)[0]
+            prompt_token.append(reasoning_result["usage"]["prompt_tokens"])
+            complete_token.append(reasoning_result["usage"]["completion_tokens"])
+            reason_path = [choice["message"]["content"] for choice in reasoning_result["choices"]]
+            message = self.prep_prompt([{"question": user_question, "examples": examples}])
+            body = self.llm._msg2req(message[0])
+            self.stm(self.workflow_instance_id)["body"] = body
+        else:
+            reason_path = []
+            for i in range(num_path):
+                reasoning_result = self.simple_infer(question=user_question,examples=examples)
+                prompt_token.append(reasoning_result["usage"]["prompt_tokens"])
+                complete_token.append(reasoning_result["usage"]["completion_tokens"])
+                reasoning_result = reasoning_result["choices"][0]["message"]["content"]
+                reason_path.append(reasoning_result)
+                if i==1:
+                    message = self.prep_prompt([{"question":user_question, "examples": examples}])
+                    body = self.llm._msg2req(message[0])
+                    self.stm(self.workflow_instance_id)["body"] = body
+        self.stm(self.workflow_instance_id)['prompt_token'] = prompt_token
+        self.stm(self.workflow_instance_id)['completion_token'] = complete_token
+        self.stm(self.workflow_instance_id)['reasoning_result'] = reason_path
+        self.callback.send_answer(self.workflow_instance_id, msg=",".join(reason_path))
+        return {'reasoning_result': reason_path}
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_reasoning/user_prompt.prompt b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_reasoning/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..138806861128a6bfd8b9e336e5b4a3c4e96b1fb5
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/agent/cot_reasoning/user_prompt.prompt
@@ -0,0 +1,11 @@
+Please reason step by step based on the following question and provide the reasoning process:
+
+question: {{question}}
+examples: {{examples}}
+
+Reasoning Steps:
+1. Analyze the question and consider possible solutions if examples is exist, based on the provided examples.
+2. Reason through the different aspects of the question step by step.
+3. Provide the reasoning process and the final answer.
+
+Please begin reasoning:
\ No newline at end of file
diff --git a/omagent_core/advanced_components/workflow/self_consist_cot/workflow.py b/omagent_core/advanced_components/workflow/self_consist_cot/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..23ec26844f2b9e171578f1e0665d4b3a7ea66c69
--- /dev/null
+++ b/omagent_core/advanced_components/workflow/self_consist_cot/workflow.py
@@ -0,0 +1,30 @@
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+
+
+
+class SelfConsistentWorkflow(ConductorWorkflow):
+
+    def __init__(self):
+        super().__init__(
+                name='self_consistent_workflow')
+    
+    def set_input(self,  user_question: str, num_path: int,examples: str = None):
+        self.user_question = user_question
+        self.num_path = num_path
+        self.examples = examples
+        self._configure_tasks()
+        self._configure_workflow()
+
+    def _configure_tasks(self):
+
+        self.reasoning_task = simple_task(task_def_name='COTReasoning', task_reference_name='cot_reasoning', inputs={'user_question': self.user_question,'num_path':self.num_path,'examples':self.examples})
+        self.extract_task = simple_task(task_def_name='COTExtract', task_reference_name='cot_extract', inputs={'reasoning_result': self.reasoning_task.output('reasoning_result')})
+        self.conclude_task = simple_task(task_def_name='COTConclusion', task_reference_name='cot_conclude', inputs={'final_answer': self.extract_task.output('final_answer'),"question":self.user_question})
+
+
+    def _configure_workflow(self):
+        
+        self   >> self.reasoning_task  >>  self.extract_task >> self.conclude_task
+        
+    
\ No newline at end of file
diff --git a/omagent_core/base.py b/omagent_core/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..5fd8d9870726daac5aaad985ad72b9b431df69b6
--- /dev/null
+++ b/omagent_core/base.py
@@ -0,0 +1,152 @@
+import contextvars
+from abc import ABC
+from typing import Any, List, Optional, Union
+
+from omagent_core.utils.container import container
+from pydantic import Field, field_validator
+from pydantic_settings import BaseSettings
+
+REQUEST_ID = contextvars.ContextVar("request_id")
+
+
+class BotBase(BaseSettings, ABC):
+    name: Optional[str] = Field(default=None, validate_default=True)
+    id: Optional[str] = Field(default=None, validate_default=True)
+    component_stm: Optional[str] = None
+    component_ltm: Optional[str] = None
+    component_callback: Optional[str] = None
+    component_input: Optional[str] = None
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    @field_validator("name", mode="before")
+    @classmethod
+    def get_type(cls, name) -> str:
+        if not name:
+            return cls.__name__
+        else:
+            return name
+
+    @property
+    def stm(self) -> str:
+        if self.component_stm is None:
+            return container.stm
+        else:
+            return container.get_component(self.component_stm)
+
+    @property
+    def ltm(self) -> str:
+        if self.component_ltm is None:
+            return container.ltm
+        else:
+            return container.get_component(self.component_ltm)
+
+    @property
+    def callback(self) -> str:
+        if self.component_callback is None:
+            return container.callback
+        else:
+            return container.get_component(self.component_callback)
+
+    @property
+    def input(self) -> str:
+        if self.component_input is None:
+            return container.input
+        else:
+            return container.get_component(self.component_input)
+
+    @classmethod
+    def get_config_template(
+        cls,
+        description: bool = True,
+        env_var: bool = True,
+        exclude_fields: List[str] = [],
+    ) -> dict:
+        template = {}
+        simple_types = (str, int, float, bool, type(None))
+
+        def is_simple_type(type_to_check: Any) -> bool:
+            return isinstance(type_to_check, type) and issubclass(
+                type_to_check, simple_types
+            )
+
+        def is_botbase_subclass(type_to_check: Any) -> bool:
+            return (
+                isinstance(type_to_check, type)
+                and issubclass(type_to_check, BotBase)
+                and type_to_check != BotBase
+            )
+
+        for field_name, field in cls.model_fields.items():
+            # Pass inner attributes
+            if field_name.startswith("_") or field_name in exclude_fields:
+                continue
+
+            field_type = field.annotation
+
+            if hasattr(field_type, "__origin__") and field_type.__origin__ is Union:
+                types = field_type.__args__
+                if any(is_botbase_subclass(t) for t in types):
+                    for t in types:
+                        if is_botbase_subclass(t):
+                            template[field_name] = t.get_config_template(
+                                description=description,
+                                env_var=env_var,
+                                exclude_fields=exclude_fields,
+                            )
+                            break
+                    continue
+                elif not all(is_simple_type(t) for t in types):
+                    continue
+            elif is_botbase_subclass(field_type):
+                template[field_name] = field_type.get_config_template(
+                    description=description,
+                    env_var=env_var,
+                    exclude_fields=exclude_fields,
+                )
+                continue
+            elif not is_simple_type(field_type):
+                continue
+
+            field_info = {"value": None}
+            if description and field.description:
+                field_info["description"] = field.description
+            if env_var:
+                field_info["env_var"] = (
+                    field.alias.upper() if field.alias else field_name.upper()
+                )
+            # Get default value for tag as <required>
+            if field.default_factory is not None:
+                field_info["value"] = field.default_factory()
+            elif field.is_required():
+                field_info["value"] = "<required>"
+            else:
+                field_info["value"] = field.default
+
+            template[field.alias if field.alias else field_name] = field_info
+            template["name"] = cls.__name__
+        return template
+
+    @classmethod
+    def from_config(cls, config_data: dict) -> "BotBase":
+        def clean_config_dict(config_dict: dict) -> dict:
+            """Recursively clean up the configuration dictionary, removing all 'description' and 'env_var' keys"""
+            cleaned = {}
+            for key, value in config_dict.items():
+                if isinstance(value, dict):
+                    if "value" in value:
+                        cleaned[key] = value["value"]
+                    else:
+                        cleaned[key] = clean_config_dict(value)
+                else:
+                    cleaned[key] = value
+            return cleaned
+
+        class_config = config_data.get(cls.__name__, {})
+        clean_class_config = clean_config_dict(class_config)
+
+        return cls(**clean_class_config)
diff --git a/omagent_core/clients/base.py b/omagent_core/clients/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b4567e1177034f8528ecb76d132c23c483822c7
--- /dev/null
+++ b/omagent_core/clients/base.py
@@ -0,0 +1,91 @@
+import datetime
+import inspect
+import re
+from abc import ABC, abstractmethod
+from pathlib import Path
+from time import time
+
+from omagent_core.base import BotBase
+from pydantic import model_validator
+
+from ..utils.error import VQLError
+from ..utils.logger import logging
+
+
+class CallbackBase(BotBase, ABC):
+    bot_id: str
+    start_time: str = datetime.datetime.now().strftime("%Y-%m-%d_%H:%M:%S")
+    # folder_name: str = f"./running_logs/{start_time}"
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        arbitrary_types_allowed = True
+        extra = "allow"
+
+    # @model_validator(mode="after")
+    # def init_folder(self):
+    #     Path(self.folder_name).mkdir(parents=True, exist_ok=True)
+
+    @abstractmethod
+    def send_block(self, **kwargs):
+        pass
+
+    @abstractmethod
+    def send_answer(self, **kwargs):
+        pass
+
+    @abstractmethod
+    def info(self, **kwargs):
+        pass
+
+    @abstractmethod
+    def error(self, **kwargs):
+        pass
+
+    @abstractmethod
+    def finish(self, **kwargs):
+        pass
+
+    def filter_special_symbols_in_msg(self, msg):
+        msg = re.sub(r"[-*#]", "", msg)
+        return msg
+
+    def remove_duplicates(self, sorted_list):
+        if not sorted_list:
+            return []
+
+        result = [sorted_list[0]]
+        for item in sorted_list[1:]:
+            if item != result[-1]:
+                result.append(item)
+        return result[::-1]
+
+    def get_calling_class(self):
+        stack = inspect.stack()
+        # Skipping the first two frames: current frame and the frame of get_calling_class_name
+        calling_chain = self.remove_duplicates(
+            [
+                each.frame.f_locals.get("self").__class__.__name__
+                for each in stack[2:]
+                if isinstance(each.frame.f_locals.get("self"), BotBase)
+            ]
+        )
+        for frame_info in stack[2:]:
+            frame = frame_info.frame
+            # Check for 'self' in local variables to identify the caller object
+            self_obj = frame.f_locals.get("self")
+            if self_obj:
+                return self_obj, calling_chain
+        return None, None
+
+
+class TestCallback(CallbackBase):
+    bot_id: str = ""
+    start_time: int = time()
+
+    def info(self, *args, **kwargs):
+        logging.debug("Callback message [{}] | [{}]".format(args, kwargs))
+
+    def error(self, error: VQLError):
+        logging.error("Error message [{}]".format(error))
diff --git a/omagent_core/clients/devices/Aaas/callback.py b/omagent_core/clients/devices/Aaas/callback.py
new file mode 100644
index 0000000000000000000000000000000000000000..e420471aafeb9bda0c9058dcfc9520951d7289a9
--- /dev/null
+++ b/omagent_core/clients/devices/Aaas/callback.py
@@ -0,0 +1,246 @@
+import json
+
+from omagent_core.clients.base import CallbackBase
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+from .schemas import ConversationEvent, InteractionType, MessageType
+
+
+@registry.register_component()
+class AaasCallback(CallbackBase):
+    redis_stream_client: RedisConnector
+    
+    bot_id: str = ""
+    
+    def _create_output_data(
+            self,
+            event='',
+            conversation_id='',
+            chat_id='',
+            agent_id='',
+            status='',
+            contentType='',
+            content='',
+            type='',
+            is_finish=True
+    ):
+        data = {
+            'content': json.dumps({
+                'event': event,
+                'data': {
+                    'conversationId': conversation_id,
+                    'chatId': chat_id,
+                    'agentId': agent_id,
+                    'createTime': None,
+                    'endTime': None,
+                    'status': status,
+                    'contentType': contentType,
+                    'content': content,
+                    'type': type,
+                    'isFinish': is_finish
+                }
+            }, ensure_ascii=False)
+        }
+        return data
+    
+    def send_base_message(
+            self,
+            event='',
+            conversation_id='',
+            chat_id='',
+            agent_id='',
+            status='',
+            contentType='',
+            content='',
+            type='',
+            is_finish=True
+    ):
+        stream_name = f"agent_os:conversation:output:{conversation_id}"
+        group_name = "OmAaasAgentConsumerGroup"  # replace with your consumer group name
+        message = self._create_output_data(
+            event=event,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status=status,
+            contentType=contentType,
+            content=content,
+            type=type,
+            is_finish=is_finish
+        )
+        self.send_to_group(stream_name, group_name, message)
+
+    def send_to_group(self, stream_name, group_name, data):
+        logging.info(f"Stream: {stream_name}, Group: {group_name}, Data: {data}")
+        self.redis_stream_client._client.xadd(stream_name, data)
+        try:
+            self.redis_stream_client._client.xgroup_create(
+                stream_name, group_name, id="0"
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+    
+    @staticmethod
+    def _parse_workflow_instance_id(data: str):
+        split_data = data.split('|')
+        if not split_data:
+            return {}
+        result = {}
+        keys = [
+            'workflow_instance_id',
+            'agent_id',
+            'conversation_id',
+            'chat_id',
+        ]
+        for index, value in enumerate(split_data):
+            if index + 1 <= len(keys):
+                result.setdefault(keys[index], value)
+        return result
+        
+    def send_incomplete(
+            self,
+            agent_id,
+            msg,
+            took=0,
+            msg_type=MessageType.TEXT.value,
+            prompt_tokens=0,
+            output_tokens=0,
+            filter_special_symbols=True,
+    ):
+        result = self._parse_workflow_instance_id(agent_id)
+        agent_id = result.get('agent_id', '')
+        conversation_id = result.get('conversation_id', '')
+        chat_id = result.get('chat_id', '')
+
+        self.send_base_message(
+            event=ConversationEvent.MESSAGE_DELTA.value,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status='delta',
+            contentType=msg_type,
+            content=msg,
+            type='answer',
+            is_finish=False
+        )
+    
+    def send_block(
+            self,
+            agent_id,
+            msg,
+            took=0,
+            msg_type=MessageType.TEXT.value,
+            interaction_type=InteractionType.DEFAULT.value,
+            prompt_tokens=0,
+            output_tokens=0,
+            filter_special_symbols=True,
+    ):
+        result = self._parse_workflow_instance_id(agent_id)
+        agent_id = result.get('agent_id', '')
+        conversation_id = result.get('conversation_id', '')
+        chat_id = result.get('chat_id', '')
+
+        if interaction_type == InteractionType.DEFAULT.INPUT:
+            self.send_base_message(
+                event=ConversationEvent.MESSAGE_DELTA.value,
+                conversation_id=conversation_id,
+                chat_id=chat_id,
+                agent_id=agent_id,
+                status='completed',
+                contentType=msg_type,
+                content=msg,
+                type='ask_complete',
+                is_finish=True
+            )
+            return
+        self.send_base_message(
+            event=ConversationEvent.MESSAGE_DELTA.value,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status='completed',
+            contentType=msg_type,
+            content=msg,
+            type='answer',
+            is_finish=True
+        )
+    
+    def send_answer(
+            self,
+            agent_id,
+            msg,
+            took=0,
+            msg_type=MessageType.TEXT.value,
+            prompt_tokens=0,
+            output_tokens=0,
+            filter_special_symbols=True,
+    ):
+        result = self._parse_workflow_instance_id(agent_id)
+        agent_id = result.get('agent_id', '')
+        conversation_id = result.get('conversation_id', '')
+        chat_id = result.get('chat_id', '')
+
+        self.send_base_message(
+            event=ConversationEvent.MESSAGE_COMPLETED.value,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status='completed',
+            contentType=msg_type,
+            content=msg,
+            type='answer',
+            is_finish=True
+        )
+    
+    def info(
+            self,
+            agent_id,
+            msg,
+            msg_type=MessageType.TEXT.value,
+    ):
+        result = self._parse_workflow_instance_id(agent_id)
+        agent_id = result.get('agent_id', '')
+        conversation_id = result.get('conversation_id', '')
+        chat_id = result.get('chat_id', '')
+
+        self.send_base_message(
+            event=ConversationEvent.MESSAGE_DELTA.value,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status='completed',
+            contentType=msg_type,
+            content=msg,
+            type='runner_output',
+            is_finish=True
+        )
+    
+    def error(
+            self,
+            agent_id,
+            msg,
+            msg_type=MessageType.TEXT.value,
+    ):
+        result = self._parse_workflow_instance_id(agent_id)
+        agent_id = result.get('agent_id', '')
+        conversation_id = result.get('conversation_id', '')
+        chat_id = result.get('chat_id', '')
+
+        self.send_base_message(
+            event=ConversationEvent.MESSAGE_ERROR.value,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status='completed',
+            contentType=msg_type,
+            content=msg,
+            type='runner_output',
+            is_finish=True
+        )
+    
+    def finish(self, agent_id, took, type, msg, prompt_tokens=0, output_tokens=0):
+        self.send_answer(
+            agent_id,
+            msg=msg
+        )
diff --git a/omagent_core/clients/devices/Aaas/client.py b/omagent_core/clients/devices/Aaas/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..b209ca5988bea5e995fc6656bafa8dddd2c1ba3b
--- /dev/null
+++ b/omagent_core/clients/devices/Aaas/client.py
@@ -0,0 +1,75 @@
+from time import sleep
+
+import yaml
+from omagent_core.clients.devices.Aaas.callback import AaasCallback
+from omagent_core.clients.devices.Aaas.input import AaasInput
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.http.models.workflow_status import terminal_status
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+registry.import_module()
+
+container.register_connector(name="redis_stream_client", connector=RedisConnector)
+# container.register_stm(stm='RedisSTM')
+container.register_callback(callback=AaasCallback)
+container.register_input(input=AaasInput)
+
+
+class AaasClient:
+    def __init__(
+        self,
+        interactor: ConductorWorkflow = None,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = [],
+    ) -> None:
+        self._interactor = interactor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+
+    def start_interactor(self):
+        worker_config = build_from_file(self._config_path)
+        self._task_handler_interactor = TaskHandler(
+            worker_config=worker_config, workers=self._workers
+        )
+        self._task_handler_interactor.start_processes()
+        # workflow_execution_id = self._interactor.start_workflow_with_input(workflow_input={})
+
+    def stop_interactor(self):
+        self._task_handler_interactor.stop_processes()
+
+    def start_processor(self):
+        workflow_instance_id = None
+        try:
+            worker_config = build_from_file(self._config_path)
+            self._task_handler_processor = TaskHandler(
+                worker_config=worker_config, workers=self._workers
+            )
+            self._task_handler_processor.start_processes()
+            workflow_instance_id = self._processor.start_workflow_with_input(
+                workflow_input={}
+            )
+            while True:
+                status = self._processor.get_workflow(
+                    workflow_id=workflow_instance_id
+                ).status
+                if status in terminal_status:
+                    workflow_instance_id = self._processor.start_workflow_with_input(
+                        workflow_input={}
+                    )
+
+                sleep(1)
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if workflow_instance_id is not None:
+                self._processor._executor.terminate(workflow_id=workflow_instance_id)
+            raise
+
+    def stop_processor(self):
+        self._task_handler_processor.stop_processes()
diff --git a/omagent_core/clients/devices/Aaas/input.py b/omagent_core/clients/devices/Aaas/input.py
new file mode 100644
index 0000000000000000000000000000000000000000..b07300a9b3f07f0069a9192dfa67bec9d1f8a82c
--- /dev/null
+++ b/omagent_core/clients/devices/Aaas/input.py
@@ -0,0 +1,283 @@
+import json
+import time
+
+from omagent_core.clients.devices.Aaas.schemas import (ConversationEvent, MessageType)
+from omagent_core.clients.input_base import InputBase
+from omagent_core.engine.http.models.workflow_status import running_status
+from omagent_core.engine.orkes.orkes_workflow_client import (
+    workflow_client)
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+
+@registry.register_component()
+class AaasInput(InputBase):
+    redis_stream_client: RedisConnector
+    
+    def read_input(self, workflow_instance_id: str, input_prompt=""):
+        result = self._parse_workflow_instance_id(workflow_instance_id)
+        workflow_instance_id = result.get('workflow_instance_id', '')
+        agent_id = result.get('agent_id', '')
+        conversation_id = result.get('conversation_id', '')
+        chat_id = result.get('chat_id', '')
+        
+        stream_name = f"agent_os:conversation:input:{workflow_instance_id}"
+        group_name = "OmAaasAgentConsumerGroup"  # consumer group name
+        consumer_name = f"{workflow_instance_id}_agent"  # consumer name
+        poll_interval: int = 1
+        
+        if input_prompt is not None:
+            start_id = self.send_output_message(agent_id, conversation_id, chat_id, input_prompt)
+        else:
+            current_timestamp = int(time.time() * 1000)
+            start_id = f"{current_timestamp}-0"
+        
+        result = {}
+        # ensure consumer group exists
+        try:
+            self.redis_stream_client._client.xgroup_create(
+                stream_name, group_name, id="0", mkstream=True
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+        
+        logging.info(
+            f"Listening to Redis stream: {stream_name} in group: {group_name} start_id: {start_id}"
+        )
+        data_flag = False
+        while True:
+            try:
+                # logging.info(f"Checking workflow status: {workflow_instance_id}")
+                workflow_status = workflow_client.get_workflow_status(
+                    workflow_instance_id
+                )
+                if workflow_status.status not in running_status:
+                    logging.info(
+                        f"Workflow {workflow_instance_id} is not running, exiting..."
+                    )
+                    break
+                
+                # read new messages from redis stream
+                messages = self.redis_stream_client._client.xrevrange(
+                    stream_name, max="+", min=start_id, count=1
+                )
+                logging.info(f"Messages: {messages}")
+                
+                # Convert byte data to string
+                messages = [
+                    (
+                        message_id,
+                        {
+                            k.decode("utf-8"): v.decode("utf-8")
+                            for k, v in message.items()
+                        },
+                    )
+                    for message_id, message in messages
+                ]
+                
+                for message_id, message in messages:
+                    data_flag = self.process_message(message, result)
+                if data_flag:
+                    break
+                # Sleep for the specified interval before checking for new messages again
+                # logging.info(f"Sleeping for {poll_interval} seconds, waiting for {stream_name} ...")
+                time.sleep(poll_interval)
+            except Exception as e:
+                logging.error(f"Error while listening to stream: {e}")
+                time.sleep(poll_interval)  # Wait before retrying
+        return result
+    
+    def process_message(self, message, result):
+        logging.info(f"Received message: {message}")
+        try:
+            payload = message.get("payload")
+            messages = []
+            for dialong in payload.get('messages', []):
+                content = []
+                for item in dialong.get('contents', []):
+                    content.append({
+                        'type': item.get('contentType', 'unknown'),
+                        'data': item.get('content')
+                    })
+                messages.append({
+                    'role': dialong.get('role'),
+                    'content': content
+                })
+            payload['messages'] = messages
+            """
+            {
+                "agent_id": "string",
+                "messages": [
+                    {
+                        "role": "string",
+                        "content": [
+                            {
+                                "type": "string",
+                                "data": "string"
+                            }
+                        ]
+                    }
+                ],
+                "kwargs": {}
+            }
+            """
+            # check payload data
+            if not payload:
+                logging.error("Payload is empty")
+                return False
+            
+            try:
+                payload_data = json.loads(payload)
+            except json.JSONDecodeError as e:
+                logging.error(f"Payload is not a valid JSON: {e}")
+                return False
+            
+            if "agent_id" not in payload_data:
+                logging.error("Payload does not contain 'agent_id' key")
+                return False
+            
+            if "messages" not in payload_data:
+                logging.error("Payload does not contain 'messages' key")
+                return False
+            
+            if not isinstance(payload_data["messages"], list):
+                logging.error("'messages' should be a list")
+                return False
+            
+            for message in payload_data["messages"]:
+                if not isinstance(message, dict):
+                    logging.error("Each item in 'messages' should be a dictionary")
+                    return False
+                if "role" not in message or "content" not in message:
+                    logging.error(
+                        "Each item in 'messages' should contain 'role' and 'content' keys"
+                    )
+                    return False
+                if not isinstance(message["content"], list):
+                    logging.error("'content' should be a list")
+                    return False
+                for content in message["content"]:
+                    if not isinstance(content, dict):
+                        logging.error("Each item in 'content' should be a dictionary")
+                        return False
+                    if "type" not in content or "data" not in content:
+                        logging.error(
+                            "Each item in 'content' should contain 'type' and 'data' keys"
+                        )
+                        return False
+            
+            message_data = json.loads(payload)
+            result.update(message_data)
+        except Exception as e:
+            logging.error(f"Error processing message: {e}")
+            return False
+        return True
+
+    @staticmethod
+    def _parse_workflow_instance_id(data: str):
+        split_data = data.split('|')
+        if not split_data:
+            return {}
+        result = {}
+        keys = [
+            'workflow_instance_id',
+            'agent_id',
+            'conversation_id',
+            'chat_id',
+        ]
+        for index, value in enumerate(split_data):
+            if index + 1 <= len(keys):
+                result.setdefault(keys[index], value)
+        return result
+    
+    def _create_output_data(
+            self,
+            event='',
+            conversation_id='',
+            chat_id='',
+            agent_id='',
+            status='',
+            contentType='',
+            content='',
+            type='',
+            is_finish=True
+    ):
+        data = {
+            'content': json.dumps({
+                'event': event,
+                'data': {
+                    'conversationId': conversation_id,
+                    'chatId': chat_id,
+                    'agentId': agent_id,
+                    'createTime': None,
+                    'endTime': None,
+                    'status': status,
+                    'contentType': contentType,
+                    'content': content,
+                    'type': type,
+                    'isFinish': is_finish
+                }
+            }, ensure_ascii=False)
+        }
+        return data
+    
+    def send_base_message(
+            self,
+            event='',
+            conversation_id='',
+            chat_id='',
+            agent_id='',
+            status='',
+            contentType='',
+            content='',
+            type='',
+            is_finish=True
+    ):
+        stream_name = f"agent_os:conversation:output:{conversation_id}"
+        group_name = "OmAaasAgentConsumerGroup"  # replace with your consumer group name
+        message = self._create_output_data(
+            event=event,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status=status,
+            contentType=contentType,
+            content=content,
+            type=type,
+            is_finish=is_finish
+        )
+        message_id = self.send_to_group(stream_name, group_name, message)
+        return message_id
+    
+    def send_output_message(
+            self,
+            agent_id,
+            conversation_id,
+            chat_id,
+            msg,
+    ):
+        return self.send_base_message(
+            event=ConversationEvent.MESSAGE_DELTA.value,
+            conversation_id=conversation_id,
+            chat_id=chat_id,
+            agent_id=agent_id,
+            status='completed',
+            contentType=MessageType.TEXT.value,
+            content=msg,
+            type='ask_complete',
+            is_finish=True
+        )
+    
+    def send_to_group(self, stream_name, group_name, data):
+        logging.info(f"Stream: {stream_name}, Group: {group_name}, Data: {data}")
+        message_id = self.redis_stream_client._client.xadd(stream_name, data)
+        try:
+            self.redis_stream_client._client.xgroup_create(
+                stream_name, group_name, id="0"
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+        
+        return message_id
+    
\ No newline at end of file
diff --git a/omagent_core/clients/devices/Aaas/schemas.py b/omagent_core/clients/devices/Aaas/schemas.py
new file mode 100644
index 0000000000000000000000000000000000000000..967a6b6b521154389b27c9aa2e71caef93ca73dc
--- /dev/null
+++ b/omagent_core/clients/devices/Aaas/schemas.py
@@ -0,0 +1,92 @@
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel
+
+
+class CodeEnum(int, Enum):
+    SUCCESS = 0
+
+
+class ContentItem(BaseModel):
+    type: str
+    resource_id: Optional[str]
+    data: str
+
+
+class InputMessage(BaseModel):
+    role: str
+    content: List[ContentItem]
+
+
+class ChatRequest(BaseModel):
+    agent_id: str
+    messages: List[InputMessage]
+    kwargs: Dict[str, Any] = {}
+
+
+class CallbackMessage(BaseModel):
+    role: str
+    type: str
+    content: str
+
+
+class Usage(BaseModel):
+    prompt_tokens: int
+    output_tokens: int
+
+
+class ContentStatus(str, Enum):
+    INCOMPLETE = "incomplete"  # the conversation content is not yet complete
+    END_BLOCK = "end_block"  # a single conversation has ended, but the overall result is not finished
+    END_ANSWER = "end_answer"  # the overall return is complete
+
+
+class ChatResponse(BaseModel):
+    agent_id: str
+    code: int
+    error_info: str
+    took: int
+    content_status: ContentStatus
+    message: CallbackMessage
+    usage: Usage
+
+
+class RunningInfo(BaseModel):
+    agent_id: str
+    progress: str
+    message: Optional[str]
+
+
+class DataMemorize(BaseModel):
+    content: List[ContentItem]
+
+
+class ContentStatus(str, Enum):
+    INCOMPLETE = "incomplete"  # the conversation content is not yet complete
+    END_BLOCK = "end_block"  # a single conversation has ended, but the overall result is not finished
+    END_ANSWER = "end_answer"  # the overall return is complete
+
+
+class InteractionType(int, Enum):
+    DEFAULT = 0
+    INPUT = 1
+
+
+class MessageType(str, Enum):
+    TEXT = "text"
+    IMAGE_URL = "image_url"
+    IMAGE_BASE64 = "image_base64"
+
+
+class ConversationEvent(str, Enum):
+    CHAT_CREATE = 'conversation.chat.created'
+    CHAT_PROCESS = 'conversation.chat.process'
+    CHAT_COMPLETED = 'conversation.chat.completed'
+    CHAT_FAILED = 'conversation.chat.failed'
+    MESSAGE_DELTA = 'conversation.message.delta'
+    MESSAGE_ERROR = 'conversation.message.error'
+    MESSAGE_COMPLETED = 'conversation.message.completed'
+    ERROR = 'error'
+    DONE = 'done'
+    
\ No newline at end of file
diff --git a/omagent_core/clients/devices/app/callback.py b/omagent_core/clients/devices/app/callback.py
new file mode 100644
index 0000000000000000000000000000000000000000..ad98f8ba212f94a2e204c195f4c8010c0fd1f602
--- /dev/null
+++ b/omagent_core/clients/devices/app/callback.py
@@ -0,0 +1,183 @@
+import json
+
+from omagent_core.clients.base import CallbackBase
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+from .schemas import CodeEnum, ContentStatus, InteractionType, MessageType
+
+
+@registry.register_component()
+class AppCallback(CallbackBase):
+    redis_stream_client: RedisConnector
+
+    bot_id: str = ""
+
+    def _create_message_data(
+        self,
+        agent_id,
+        code,
+        error_info,
+        took,
+        msg_type,
+        msg,
+        content_status,
+        interaction_type,
+        prompt_tokens,
+        output_tokens,
+        filter_special_symbols=True,
+    ):
+        if msg_type == MessageType.TEXT.value and filter_special_symbols:
+            msg = self.filter_special_symbols_in_msg(msg)
+        message = {"role": "assistant", "type": msg_type, "content": msg}
+        usage = {"prompt_tokens": prompt_tokens, "output_tokens": output_tokens}
+        data = {
+            "agent_id": agent_id,
+            "code": code,
+            "error_info": error_info,
+            "took": took,
+            "content_status": content_status,
+            "interaction_type": int(interaction_type),
+            "message": message,
+            "usage": usage,
+        }
+        return {"payload": json.dumps(data, ensure_ascii=False)}
+
+    def send_to_group(self, stream_name, group_name, data):
+        logging.info(f"Stream: {stream_name}, Group: {group_name}, Data: {data}")
+        self.redis_stream_client._client.xadd(stream_name, data)
+        try:
+            self.redis_stream_client._client.xgroup_create(
+                stream_name, group_name, id="0"
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+
+    def send_base_message(
+        self,
+        agent_id,
+        code,
+        error_info,
+        took,
+        msg_type,
+        msg,
+        content_status,
+        interaction_type,
+        prompt_tokens,
+        output_tokens,
+        filter_special_symbols=True,
+    ):
+        stream_name = f"{agent_id}_output"
+        group_name = "omappagent"  # replace with your consumer group name
+        data = self._create_message_data(
+            agent_id,
+            code,
+            error_info,
+            took,
+            msg_type,
+            msg,
+            content_status,
+            interaction_type,
+            prompt_tokens,
+            output_tokens,
+            filter_special_symbols,
+        )
+        self.send_to_group(stream_name, group_name, data)
+
+    def send_incomplete(
+        self,
+        agent_id,
+        msg,
+        took=0,
+        msg_type=MessageType.TEXT.value,
+        prompt_tokens=0,
+        output_tokens=0,
+        filter_special_symbols=True,
+    ):
+        self.send_base_message(
+            agent_id,
+            CodeEnum.SUCCESS.value,
+            "",
+            took,
+            msg_type,
+            msg,
+            ContentStatus.INCOMPLETE.value,
+            InteractionType.DEFAULT.value,
+            prompt_tokens,
+            output_tokens,
+            filter_special_symbols,
+        )
+
+    def send_block(
+        self,
+        agent_id,
+        msg,
+        took=0,
+        msg_type=MessageType.TEXT.value,
+        interaction_type=InteractionType.DEFAULT.value,
+        prompt_tokens=0,
+        output_tokens=0,
+        filter_special_symbols=True,
+    ):
+        self.send_base_message(
+            agent_id,
+            CodeEnum.SUCCESS.value,
+            "",
+            took,
+            msg_type,
+            msg,
+            ContentStatus.END_BLOCK.value,
+            interaction_type,
+            prompt_tokens,
+            output_tokens,
+            filter_special_symbols,
+        )
+
+    def send_answer(
+        self,
+        agent_id,
+        msg,
+        took=0,
+        msg_type=MessageType.TEXT.value,
+        prompt_tokens=0,
+        output_tokens=0,
+        filter_special_symbols=True,
+    ):
+        self.send_base_message(
+            agent_id,
+            CodeEnum.SUCCESS.value,
+            "",
+            took,
+            msg_type,
+            msg,
+            ContentStatus.END_ANSWER.value,
+            InteractionType.DEFAULT.value,
+            prompt_tokens,
+            output_tokens,
+            filter_special_symbols,
+        )
+
+    def info(self, agent_id, progress, message):
+        stream_name = f"{agent_id}_running"
+        data = {"agent_id": agent_id, "progress": progress, "message": message}
+        payload = {"payload": json.dumps(data, ensure_ascii=False)}
+        self.redis_stream_client._client.xadd(stream_name, payload)
+
+    def error(self, agent_id, error_code, error_info, prompt_tokens=0, output_tokens=0):
+        self.send_base_message(
+            agent_id,
+            error_code,
+            error_info,
+            0,
+            MessageType.TEXT.value,
+            "",
+            ContentStatus.END_ANSWER.value,
+            InteractionType.DEFAULT.value,
+            prompt_tokens,
+            output_tokens,
+        )
+
+    def finish(self, agent_id, took, type, msg, prompt_tokens=0, output_tokens=0):
+        self.send_answer(agent_id, took, type, msg, prompt_tokens, output_tokens)
diff --git a/omagent_core/clients/devices/app/client.py b/omagent_core/clients/devices/app/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..4055326bf8a33c1e750a646291f2512b511fc165
--- /dev/null
+++ b/omagent_core/clients/devices/app/client.py
@@ -0,0 +1,75 @@
+from time import sleep
+
+import yaml
+from omagent_core.clients.devices.app.callback import AppCallback
+from omagent_core.clients.devices.app.input import AppInput
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.http.models.workflow_status import terminal_status
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+registry.import_module()
+
+container.register_connector(name="redis_stream_client", connector=RedisConnector)
+# container.register_stm(stm='RedisSTM')
+container.register_callback(callback=AppCallback)
+container.register_input(input=AppInput)
+
+
+class AppClient:
+    def __init__(
+        self,
+        interactor: ConductorWorkflow = None,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = [],
+    ) -> None:
+        self._interactor = interactor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+
+    def start_interactor(self):
+        worker_config = build_from_file(self._config_path)
+        self._task_handler_interactor = TaskHandler(
+            worker_config=worker_config, workers=self._workers
+        )
+        self._task_handler_interactor.start_processes()
+        # workflow_execution_id = self._interactor.start_workflow_with_input(workflow_input={})
+
+    def stop_interactor(self):
+        self._task_handler_interactor.stop_processes()
+
+    def start_processor(self):
+        workflow_instance_id = None
+        try:
+            worker_config = build_from_file(self._config_path)
+            self._task_handler_processor = TaskHandler(
+                worker_config=worker_config, workers=self._workers
+            )
+            self._task_handler_processor.start_processes()
+            workflow_instance_id = self._processor.start_workflow_with_input(
+                workflow_input={}
+            )
+            while True:
+                status = self._processor.get_workflow(
+                    workflow_id=workflow_instance_id
+                ).status
+                if status in terminal_status:
+                    workflow_instance_id = self._processor.start_workflow_with_input(
+                        workflow_input={}
+                    )
+
+                sleep(1)
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if workflow_instance_id is not None:
+                self._processor._executor.terminate(workflow_id=workflow_instance_id)
+            raise
+
+    def stop_processor(self):
+        self._task_handler_processor.stop_processes()
diff --git a/omagent_core/clients/devices/app/image_index.py b/omagent_core/clients/devices/app/image_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..2f4830347e669a674e60669a750c6522626d722e
--- /dev/null
+++ b/omagent_core/clients/devices/app/image_index.py
@@ -0,0 +1,132 @@
+import asyncio
+import json
+import time
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models.workflow_status import running_status
+from omagent_core.engine.orkes.orkes_workflow_client import workflow_client
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+
+@registry.register_worker()
+class ImageIndexListener(BaseWorker):
+    def _run(self):
+        stream_name = f"image_process"
+        group_name = "omappagent"  # consumer group name
+        consumer_name = f"image_agent"  # consumer name
+        poll_interval: int = 1
+        current_timestamp = int(time.time() * 1000)
+        start_id = f"{current_timestamp}-0"
+
+        result = {}
+        # ensure consumer group exists
+        try:
+            container.get_connector("redis_stream_client")._client.xgroup_create(
+                stream_name, group_name, id="0", mkstream=True
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+
+        logging.info(f"Listening to Redis stream: {stream_name} in group: {group_name}")
+        flag = False
+        while True:
+            try:
+                # logging.info(f"Checking workflow status: {self.workflow_instance_id}")
+                workflow_status = workflow_client.get_workflow_status(
+                    self.workflow_instance_id
+                )
+                if workflow_status.status not in running_status:
+                    logging.info(
+                        f"Workflow {self.workflow_instance_id} is not running, exiting..."
+                    )
+                    break
+
+                # read new messages from redis stream
+                messages = container.get_connector(
+                    "redis_stream_client"
+                )._client.xrevrange(stream_name, max="+", min=start_id, count=1)
+                # Convert byte data to string
+                messages = [
+                    (
+                        message_id,
+                        {
+                            k.decode("utf-8"): v.decode("utf-8")
+                            for k, v in message.items()
+                        },
+                    )
+                    for message_id, message in messages
+                ]
+                # logging.info(f"Messages: {messages}")
+
+                for message_id, message in messages:
+                    flag = self.process_message(message, result)
+                    # confirm message has been processed
+                    container.get_connector("redis_stream_client")._client.xack(
+                        stream_name, group_name, message_id
+                    )
+                if flag:
+                    break
+                # Sleep for the specified interval before checking for new messages again
+                # logging.info(f"Sleeping for {poll_interval} seconds, waiting for {stream_name} ...")
+                time.sleep(poll_interval)
+            except Exception as e:
+                logging.error(f"Error while listening to stream: {e}")
+                time.sleep(poll_interval)  # Wait before retrying
+        return {"output": result}
+
+    def process_message(self, message, result):
+        logging.info(f"Received message: {message}")
+        try:
+            payload = message.get("payload")
+            """
+            {
+                "content": [
+                    {
+                    "type": "string",
+                    "resource_id": "string",
+                    "data": "string"
+                    }
+                ]
+            }
+            """
+            # check payload data
+            if not payload:
+                logging.error("Payload is empty")
+                return False
+
+            try:
+                payload_data = json.loads(payload)
+            except json.JSONDecodeError as e:
+                logging.error(f"Payload is not a valid JSON: {e}")
+                return False
+
+            if "content" not in payload_data:
+                logging.error("Payload does not contain 'content' key")
+                return False
+
+            if not isinstance(payload_data["content"], list):
+                logging.error("'content' should be a list")
+                return False
+
+            for item in payload_data["content"]:
+                if not isinstance(item, dict):
+                    logging.error("Each item in 'content' should be a dictionary")
+                    return False
+                if (
+                    "type" not in item
+                    or "resource_id" not in item
+                    or "data" not in item
+                ):
+                    logging.error(
+                        "Each item in 'content' should contain 'type', 'resource_id' and 'data' keys"
+                    )
+                    return False
+            message_data = json.loads(payload)
+            result.update(message_data)
+        except Exception as e:
+            logging.error(f"Error processing message: {e}")
+            return False
+        return True
diff --git a/omagent_core/clients/devices/app/input.py b/omagent_core/clients/devices/app/input.py
new file mode 100644
index 0000000000000000000000000000000000000000..809187f48465ae9d3aa234f14edfcb9dbd7f912f
--- /dev/null
+++ b/omagent_core/clients/devices/app/input.py
@@ -0,0 +1,247 @@
+import json
+import time
+
+from omagent_core.clients.devices.app.schemas import (CodeEnum, ContentStatus,
+                                                      InteractionType,
+                                                      MessageType)
+from omagent_core.clients.input_base import InputBase
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models.workflow_status import running_status
+from omagent_core.engine.orkes.orkes_workflow_client import (
+    OrkesWorkflowClient, workflow_client)
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils import registry
+from omagent_core.utils.container import container
+from omagent_core.utils.general import read_image
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+import os
+
+@registry.register_component()
+class AppInput(InputBase):
+    redis_stream_client: RedisConnector
+
+    def read_input(self, workflow_instance_id: str, input_prompt=""):
+        stream_name = f"{workflow_instance_id}_input"
+        group_name = "omappagent"  # consumer group name
+        consumer_name = f"{workflow_instance_id}_agent"  # consumer name
+        poll_interval: int = 1
+
+        if input_prompt is not None:
+            start_id = self._send_input_message(workflow_instance_id, input_prompt)
+        else:
+            current_timestamp = int(time.time() * 1000)
+            start_id = f"{current_timestamp}-0"
+
+        result = {}
+        # ensure consumer group exists
+        try:
+            self.redis_stream_client._client.xgroup_create(
+                stream_name, group_name, id="0", mkstream=True
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+
+        if not os.getenv("OMAGENT_MODE") == "lite":
+            logging.info(
+                f"Listening to Redis stream: {stream_name} in group: {group_name} start_id: {start_id}"
+            )
+        
+        data_flag = False
+        while True:
+            try:
+                # logging.info(f"Checking workflow status: {workflow_instance_id}")
+                workflow_status = workflow_client.get_workflow_status(
+                    workflow_instance_id
+                )
+                if workflow_status.status not in running_status:
+                    logging.info(
+                        f"Workflow {workflow_instance_id} is not running, exiting..."
+                    )
+                    break
+
+                # read new messages from redis stream
+                messages = self.redis_stream_client._client.xrevrange(
+                    stream_name, max="+", min=start_id, count=1
+                )
+                # Convert byte data to string
+                messages = [
+                    (
+                        message_id,
+                        {
+                            k.decode("utf-8"): v.decode("utf-8")
+                            for k, v in message.items()
+                        },
+                    )
+                    for message_id, message in messages
+                ]
+                # logging.info(f"Messages: {messages}")
+
+                for message_id, message in messages:
+                    data_flag = self.process_message(message, result)
+                if data_flag:
+                    break
+                # Sleep for the specified interval before checking for new messages again
+                # logging.info(f"Sleeping for {poll_interval} seconds, waiting for {stream_name} ...")
+                time.sleep(poll_interval)
+            except Exception as e:
+                logging.error(f"Error while listening to stream: {e}")
+                time.sleep(poll_interval)  # Wait before retrying
+        return result
+
+    def process_message(self, message, result):
+        logging.info(f"Received message: {message}")
+        try:
+            payload = message.get("payload")
+            """
+            {
+                "agent_id": "string",
+                "messages": [
+                    {
+                        "role": "string",
+                        "content": [
+                            {
+                                "type": "string",
+                                "data": "string"
+                            }
+                        ]
+                    }
+                ],
+                "kwargs": {}
+            }
+            """
+            # check payload data
+            if not payload:
+                logging.error("Payload is empty")
+                return False
+
+            try:
+                payload_data = json.loads(payload)
+            except json.JSONDecodeError as e:
+                logging.error(f"Payload is not a valid JSON: {e}")
+                return False
+
+            if "agent_id" not in payload_data:
+                logging.error("Payload does not contain 'agent_id' key")
+                return False
+
+            if "messages" not in payload_data:
+                logging.error("Payload does not contain 'messages' key")
+                return False
+
+            if not isinstance(payload_data["messages"], list):
+                logging.error("'messages' should be a list")
+                return False
+
+            for message in payload_data["messages"]:
+                if not isinstance(message, dict):
+                    logging.error("Each item in 'messages' should be a dictionary")
+                    return False
+                if "role" not in message or "content" not in message:
+                    logging.error(
+                        "Each item in 'messages' should contain 'role' and 'content' keys"
+                    )
+                    return False
+                if not isinstance(message["content"], list):
+                    logging.error("'content' should be a list")
+                    return False
+                for content in message["content"]:
+                    if not isinstance(content, dict):
+                        logging.error("Each item in 'content' should be a dictionary")
+                        return False
+                    if "type" not in content or "data" not in content:
+                        logging.error(
+                            "Each item in 'content' should contain 'type' and 'data' keys"
+                        )
+                        return False
+
+            message_data = json.loads(payload)
+            result.update(message_data)
+        except Exception as e:
+            logging.error(f"Error processing message: {e}")
+            return False
+        return True
+
+    def _send_input_message(self, agent_id, msg):
+        message_id = self._send_base_message(
+            agent_id,
+            CodeEnum.SUCCESS.value,
+            "",
+            0,
+            MessageType.TEXT.value,
+            msg,
+            ContentStatus.END_BLOCK.value,
+            InteractionType.INPUT.value,
+            0,
+            0,
+        )
+        return message_id
+
+    def _create_message_data(
+        self,
+        agent_id,
+        code,
+        error_info,
+        took,
+        msg_type,
+        msg,
+        content_status,
+        interaction_type,
+        prompt_tokens,
+        output_tokens,
+    ):
+        message = {"role": "assistant", "type": msg_type, "content": msg}
+        usage = {"prompt_tokens": prompt_tokens, "output_tokens": output_tokens}
+        data = {
+            "agent_id": agent_id,
+            "code": code,
+            "error_info": error_info,
+            "took": took,
+            "content_status": content_status,
+            "interaction_type": int(interaction_type),
+            "message": message,
+            "usage": usage,
+        }
+        return {"payload": json.dumps(data, ensure_ascii=False)}
+
+    def _send_to_group(self, stream_name, group_name, data):
+        logging.info(f"Stream: {stream_name}, Group: {group_name}, Data: {data}")
+        message_id = self.redis_stream_client._client.xadd(stream_name, data)
+        try:
+            self.redis_stream_client._client.xgroup_create(
+                stream_name, group_name, id="0"
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+
+        return message_id
+
+    def _send_base_message(
+        self,
+        agent_id,
+        code,
+        error_info,
+        took,
+        msg_type,
+        msg,
+        content_status,
+        interaction_type,
+        prompt_tokens,
+        output_tokens,
+    ):
+        stream_name = f"{agent_id}_output"
+        group_name = "omappagent"  # replace with your consumer group name
+        data = self._create_message_data(
+            agent_id,
+            code,
+            error_info,
+            took,
+            msg_type,
+            msg,
+            content_status,
+            interaction_type,
+            prompt_tokens,
+            output_tokens,
+        )
+        message_id = self._send_to_group(stream_name, group_name, data)
+        return message_id
diff --git a/omagent_core/clients/devices/app/schemas.py b/omagent_core/clients/devices/app/schemas.py
new file mode 100644
index 0000000000000000000000000000000000000000..111442b2787acbeae17e5b8162f5cee965f8dd08
--- /dev/null
+++ b/omagent_core/clients/devices/app/schemas.py
@@ -0,0 +1,79 @@
+from enum import Enum
+from typing import Any, Dict, List, Optional
+
+from pydantic import BaseModel
+
+
+class CodeEnum(int, Enum):
+    SUCCESS = 0
+
+
+class ContentItem(BaseModel):
+    type: str
+    resource_id: Optional[str]
+    data: str
+
+
+class InputMessage(BaseModel):
+    role: str
+    content: List[ContentItem]
+
+
+class ChatRequest(BaseModel):
+    agent_id: str
+    messages: List[InputMessage]
+    kwargs: Dict[str, Any] = {}
+
+
+class CallbackMessage(BaseModel):
+    role: str
+    type: str
+    content: str
+
+
+class Usage(BaseModel):
+    prompt_tokens: int
+    output_tokens: int
+
+
+class ContentStatus(str, Enum):
+    INCOMPLETE = "incomplete"  # the conversation content is not yet complete
+    END_BLOCK = "end_block"  # a single conversation has ended, but the overall result is not finished
+    END_ANSWER = "end_answer"  # the overall return is complete
+
+
+class ChatResponse(BaseModel):
+    agent_id: str
+    code: int
+    error_info: str
+    took: int
+    content_status: ContentStatus
+    message: CallbackMessage
+    usage: Usage
+
+
+class RunningInfo(BaseModel):
+    agent_id: str
+    progress: str
+    message: Optional[str]
+
+
+class DataMemorize(BaseModel):
+    content: List[ContentItem]
+
+
+class ContentStatus(str, Enum):
+    INCOMPLETE = "incomplete"  # the conversation content is not yet complete
+    END_BLOCK = "end_block"  # a single conversation has ended, but the overall result is not finished
+    END_ANSWER = "end_answer"  # the overall return is complete
+
+
+class InteractionType(int, Enum):
+    DEFAULT = 0
+    INPUT = 1
+
+
+class MessageType(str, Enum):
+    TEXT = "text"
+    IMAGE_URL = "image_url"
+    IMAGE_BASE64 = "image_base64"
diff --git a/omagent_core/clients/devices/cli/__init__.py b/omagent_core/clients/devices/cli/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ece8a065b90d70353165a74af425c304e418132
--- /dev/null
+++ b/omagent_core/clients/devices/cli/__init__.py
@@ -0,0 +1,11 @@
+import os
+
+env = os.getenv("OMAGENT_MODE", "lite").lower()
+
+if env == "lite":
+    print ("importing lite client") 
+    from .lite_client import DefaultClient
+else:
+    print ("importing pro client")
+    from .client import DefaultClient
+
diff --git a/omagent_core/clients/devices/cli/callback.py b/omagent_core/clients/devices/cli/callback.py
new file mode 100644
index 0000000000000000000000000000000000000000..d6ad3a9071b6a6c72b424582dbde1fbfa30ca18e
--- /dev/null
+++ b/omagent_core/clients/devices/cli/callback.py
@@ -0,0 +1,85 @@
+import datetime
+import os
+import sys
+
+from colorama import Fore, Style
+from omagent_core.clients.base import CallbackBase
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+
+@registry.register_component()
+class DefaultCallback(CallbackBase):
+    bot_id: str = ""
+    incomplete_flag: bool = False
+
+    def visualize_in_terminal(self, *args, **kwargs):
+        pass
+
+    def info(self, agent_id, progress, message):
+        logging.info(
+            f"\n{Fore.BLUE}info:{agent_id} {progress} {message}{Style.RESET_ALL}"
+        )
+
+    def send_incomplete(self, agent_id, msg, **kwargs):
+        sys.stdout.write(f"{Fore.BLUE}{msg}{Style.RESET_ALL}")
+        sys.stdout.flush()
+        self.incomplete_flag = True
+
+    def send_block(self, agent_id, msg, **kwargs):
+        if kwargs.get("filter_special_symbols", False):
+            msg = self.filter_special_symbols_in_msg(msg)
+        if self.incomplete_flag:
+            sys.stdout.write(f"{Fore.BLUE}{msg}{Style.RESET_ALL}")
+            sys.stdout.flush()
+            self.incomplete_flag = False
+        else:
+            logging.info(f"\n{Fore.BLUE}block:{msg}{Style.RESET_ALL}")
+
+    def error(self, agent_id, error_code, error_info, **kwargs):
+        logging.error(f"\n{Fore.RED}{error_info}{Style.RESET_ALL}")
+
+    def send_answer(self, agent_id, msg, **kwargs):
+        if kwargs.get("filter_special_symbols", False):
+            msg = self.filter_special_symbols_in_msg(msg)
+        if self.incomplete_flag:
+            sys.stdout.write(f"{Fore.BLUE}{msg}{Style.RESET_ALL}")
+            sys.stdout.flush()
+            self.incomplete_flag = False
+        else:
+            logging.info(f"\n{Fore.BLUE}answer:{msg}{Style.RESET_ALL}")
+
+    def finish(self, **kwargs):
+        def generate_tree(path, indent=""):
+            tree_str = ""
+            items = sorted(
+                [
+                    item
+                    for item in os.listdir(path)
+                    if os.path.isdir(os.path.join(path, item))
+                ]
+            )
+            for i, item in enumerate(items):
+                tree_str += f"{indent}|-- {item}\n"
+                new_path = os.path.join(path, item)
+                if os.path.isdir(new_path):
+                    if i == len(items) - 1:
+                        tree_str += generate_tree(new_path, indent + "    ")
+                    else:
+                        tree_str += generate_tree(new_path, indent + "|   ")
+            return tree_str
+
+        execution_flow = generate_tree(self.folder_name)
+        with open(f"{self.folder_name}/execution_flow.txt", "w") as file:
+            file.write(execution_flow)
+        logging.info(
+            f"{Fore.BLUE}Finish running at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
+            f"Execution flow as follow:\n{execution_flow}"
+            f"{Style.RESET_ALL}"
+        )
+
+    def send_markdown_data(self, data):
+        import requests
+
+        data = {"message": data}
+        requests.post(self.endpoint, json=data)
diff --git a/omagent_core/clients/devices/cli/client.py b/omagent_core/clients/devices/cli/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fb5fedbf06818cf6694c752ccc2a528b19276fc
--- /dev/null
+++ b/omagent_core/clients/devices/cli/client.py
@@ -0,0 +1,311 @@
+from pathlib import Path
+
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.container import container
+
+container.register_connector(name="redis_stream_client", connector=RedisConnector)
+import json
+from time import sleep
+
+import yaml
+from colorama import Fore, Style
+from omagent_core.clients.devices.app.input import AppInput
+from omagent_core.clients.devices.cli.callback import DefaultCallback
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.http.models.workflow_status import (running_status,
+                                                             terminal_status)
+from omagent_core.engine.orkes.orkes_workflow_client import workflow_client
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+registry.import_module()
+
+# container.register_stm(stm='RedisSTM')
+container.register_callback(callback=DefaultCallback)
+container.register_input(input=AppInput)
+
+
+class DefaultClient:
+    def __init__(
+        self,
+        interactor: ConductorWorkflow = None,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = [],
+        input_prompt: str = None,
+    ) -> None:
+        self._interactor = interactor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+        self._input_prompt = input_prompt
+        self._task_to_domain = {}
+
+    def start_interactor(self):
+        workflow_instance_id = None
+        try:
+            absolute_path = Path(self._config_path).resolve()
+            worker_config = build_from_file(self._config_path)
+            self._task_handler_interactor = TaskHandler(
+                worker_config=worker_config, workers=self._workers, task_to_domain=self._task_to_domain
+            )
+            self._task_handler_interactor.start_processes()
+            workflow_instance_id = self._interactor.start_workflow_with_input(
+                workflow_input={}, task_to_domain=self._task_to_domain
+            )
+
+            stream_name = f"{workflow_instance_id}_output"
+            consumer_name = f"{workflow_instance_id}_agent"  # consumer name
+            group_name = "omappagent"  # replace with your consumer group name
+            poll_interval = 1
+
+            if self._input_prompt:
+                self.first_input(
+                    workflow_instance_id=workflow_instance_id,
+                    input_prompt=self._input_prompt,
+                )
+
+            try:
+                container.get_connector("redis_stream_client")._client.xgroup_create(
+                    stream_name, group_name, id="0", mkstream=True
+                )
+            except Exception as e:
+                logging.debug(f"Consumer group may already exist: {e}")
+
+            while True:
+                try:
+                    status = self._interactor.get_workflow(
+                        workflow_id=workflow_instance_id
+                    ).status
+                    if status in terminal_status:
+                        break
+                    data_flag = False
+                    content = None
+                    # logging.info(f"Checking workflow status: {workflow_instance_id}")
+                    workflow_status = workflow_client.get_workflow_status(
+                        workflow_instance_id
+                    )
+                    if workflow_status.status not in running_status:
+                        logging.info(
+                            f"Workflow {workflow_instance_id} is not running, exiting..."
+                        )
+                        break
+
+                    # read new messages from consumer group
+                    messages = container.get_connector(
+                        "redis_stream_client"
+                    )._client.xreadgroup(
+                        group_name, consumer_name, {stream_name: ">"}, count=1
+                    )
+                    # Convert byte data to string
+                    messages = [
+                        (
+                            stream,
+                            [
+                                (
+                                    message_id,
+                                    {
+                                        k.decode("utf-8"): v.decode("utf-8")
+                                        for k, v in message.items()
+                                    },
+                                )
+                                for message_id, message in message_list
+                            ],
+                        )
+                        for stream, message_list in messages
+                    ]
+                    # logging.info(f"Messages: {messages}")
+
+                    for stream, message_list in messages:
+                        for message_id, message in message_list:
+                            data_flag, content = self.process_message(message)
+                            # confirm message has been processed
+                            container.get_connector("redis_stream_client")._client.xack(
+                                stream_name, group_name, message_id
+                            )
+                    if data_flag:
+                        contents = []
+                        while True:
+                            print(
+                                f"{Fore.GREEN}{content}(Waiting for input. Your input can only be text or image path each time, you can press Enter once to input multiple times. Press Enter twice to finish the entire input.):{Style.RESET_ALL}"
+                            )
+                            user_input_lines = []
+                            while True:
+                                line = input(f"{Fore.GREEN}>>>{Style.RESET_ALL}")
+                                if line == "":
+                                    break
+                                user_input_lines.append(line)
+                            logging.info(f"User input lines: {user_input_lines}")
+
+                            for user_input in user_input_lines:
+                                if self.is_url(user_input) or self.is_file(user_input):
+                                    contents.append(
+                                        {"type": "image_url", "data": user_input}
+                                    )
+                                else:
+                                    contents.append(
+                                        {"type": "text", "data": user_input}
+                                    )
+                            if len(contents) > 0:
+                                break
+                        result = {
+                            "agent_id": workflow_instance_id,
+                            "messages": [{"role": "user", "content": contents}],
+                            "kwargs": {},
+                        }
+                        container.get_connector("redis_stream_client")._client.xadd(
+                            f"{workflow_instance_id}_input",
+                            {"payload": json.dumps(result, ensure_ascii=False)},
+                        )
+                    # Sleep for the specified interval before checking for new messages again
+                    # logging.info(f"Sleeping for {poll_interval} seconds, waiting for {stream_name} ...")
+                    sleep(poll_interval)
+                except Exception as e:
+                    logging.error(f"Error while listening to stream: {e}")
+                    sleep(poll_interval)  # Wait before retrying
+            self.stop_interactor()
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if workflow_instance_id is not None:
+                self._interactor._executor.terminate(workflow_id=workflow_instance_id)
+            raise
+
+    def stop_interactor(self):
+        self._task_handler_interactor.stop_processes()
+
+    def start_processor(self):
+        workflow_instance_id = None
+        try:
+            worker_config = build_from_file(self._config_path)
+            self._task_handler_processor = TaskHandler(
+                worker_config=worker_config, workers=self._workers, task_to_domain=self._task_to_domain
+            )
+            self._task_handler_processor.start_processes()
+            workflow_instance_id = self._processor.start_workflow_with_input(
+                workflow_input={}, task_to_domain=self._task_to_domain
+            )
+            user_input = input(
+                f"{Fore.GREEN}Please input a folder path of images:(WaitPress Enter to finish the entire input.):\n>>>{Style.RESET_ALL}"
+            )
+
+            image_items = []
+            idx = 0
+            for image_file in Path(user_input).iterdir():
+                if image_file.is_file() and image_file.suffix in [
+                    ".png",
+                    ".jpg",
+                    ".jpeg",
+                ]:
+                    image_items.append(
+                        {
+                            "type": "image_url",
+                            "resource_id": str(idx),
+                            "data": str(image_file),
+                        }
+                    )
+                    idx += 1
+            result = {"content": image_items}
+            container.get_connector("redis_stream_client")._client.xadd(
+                f"image_process", {"payload": json.dumps(result, ensure_ascii=False)}
+            )
+            while True:
+                status = self._processor.get_workflow(
+                    workflow_id=workflow_instance_id
+                ).status
+                if status in terminal_status:
+                    break
+
+                sleep(1)
+            self.stop_processor()
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if workflow_instance_id is not None:
+                self._processor._executor.terminate(workflow_id=workflow_instance_id)
+            raise
+
+    def stop_processor(self):
+        self._task_handler_processor.stop_processes()
+
+    def process_message(self, message):
+        logging.info(f"Received message: {message}")
+        try:
+            payload = message.get("payload")
+            # check payload data
+            if not payload:
+                logging.error("Payload is empty")
+                return False, None
+
+            try:
+                payload_data = json.loads(payload)
+            except json.JSONDecodeError as e:
+                logging.error(f"Payload is not a valid JSON: {e}")
+                return False, None
+
+            if (
+                "interaction_type" in payload_data
+                and payload_data["interaction_type"] == 1
+            ):
+                content = payload_data["message"]["content"]
+                return True, content
+
+        except Exception as e:
+            logging.error(f"Error processing message: {e}")
+            return False, None
+        return False, None
+
+    def is_file(self, path: str) -> bool:
+        """
+        Determine if the given string is a file
+
+        :param path: File path string
+        :return: Returns True if it is a file, otherwise returns False
+        """
+        import os
+
+        try:
+            return os.path.isfile(path)
+        except Exception as e:
+            logging.error(f"Error checking if path is a file: {e}")
+            return False
+
+    def is_url(self, url: str) -> bool:
+        """
+        Determine if the given string is a URL
+        """
+        import re
+
+        return bool(re.match(r"^https?://", url))
+
+    def first_input(self, workflow_instance_id: str, input_prompt=""):
+        contents = []
+        while True:
+            print(
+                f"{Fore.GREEN}{input_prompt}(Waiting for input. Your input can only be text or image path each time, you can press Enter once to input multiple times. Press Enter twice to finish the entire input.):{Style.RESET_ALL}"
+            )
+            user_input_lines = []
+            while True:
+                line = input(f"{Fore.GREEN}>>>{Style.RESET_ALL}")
+                if line == "":
+                    break
+                user_input_lines.append(line)
+            logging.info(f"User input lines: {user_input_lines}")
+
+            for user_input in user_input_lines:
+                if self.is_url(user_input) or self.is_file(user_input):
+                    contents.append({"type": "image_url", "data": user_input})
+                else:
+                    contents.append({"type": "text", "data": user_input})
+            if len(contents) > 0:
+                break
+        result = {
+            "agent_id": workflow_instance_id,
+            "messages": [{"role": "user", "content": contents}],
+            "kwargs": {},
+        }
+        container.get_connector("redis_stream_client")._client.xadd(
+            f"{workflow_instance_id}_input",
+            {"payload": json.dumps(result, ensure_ascii=False)},
+        )
diff --git a/omagent_core/clients/devices/cli/lite_client.py b/omagent_core/clients/devices/cli/lite_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..3ccd1b18b5a69995edca6bdac11a743743a65ebb
--- /dev/null
+++ b/omagent_core/clients/devices/cli/lite_client.py
@@ -0,0 +1,348 @@
+from pathlib import Path
+import uuid
+
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.container import container
+
+container.register_connector(name="redis_stream_client", connector=RedisConnector)
+import json
+from time import sleep
+
+import yaml
+from colorama import Fore, Style
+from omagent_core.clients.devices.app.input import AppInput
+from omagent_core.clients.devices.cli.callback import DefaultCallback
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.http.models.workflow_status import (running_status,
+                                                             terminal_status)
+from omagent_core.engine.orkes.orkes_workflow_client import workflow_client
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+import os
+import sys
+import queue
+
+
+registry.import_module()
+
+# container.register_stm(stm='RedisSTM')
+container.register_callback(callback=DefaultCallback)
+container.register_input(input=AppInput)
+
+
+class DefaultClient:
+    def __init__(
+        self,
+        interactor: ConductorWorkflow = None,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = [],
+        input_prompt: str = None,
+    ) -> None:
+        self._interactor = interactor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+        self._input_prompt = input_prompt
+        self._task_to_domain = {}
+        worker_config = build_from_file(self._config_path)
+        self.workflow_instance_id = str(uuid.uuid4())
+        self.initialization(workers, worker_config)
+
+    def initialization(self, workers, worker_config):        
+        self.workers = {}
+        for worker in workers:
+            worker.workflow_instance_id = self.workflow_instance_id
+            self.workers[type(worker).__name__] = worker            
+        
+        for config in worker_config:
+            worker_cls = registry.get_worker(config['name'])    
+            worker = worker_cls(**config)
+            worker.workflow_instance_id = self.workflow_instance_id
+            self.workers[config['name']] = worker
+
+    def start_interactor(self):
+        import threading
+        from time import sleep
+
+        workflow_instance_id = self.workflow_instance_id
+        exception_queue = queue.Queue()  # add exception queue
+
+        try:
+            absolute_path = Path(self._config_path).resolve()
+            worker_config = build_from_file(self._config_path)
+            # ---------------------------------------------------
+            def run_workflow():
+                try:
+                    self._interactor.start_workflow_with_input(
+                        workflow_input={}, workers=self.workers
+                    )
+                except Exception as e:
+                    exception_queue.put(e)  # add exception to queue
+                    logging.error(f"Error starting workflow: {e}")
+                    raise e
+            workflow_thread = threading.Thread(target=run_workflow, daemon=True)
+            workflow_thread.start()
+            # Wait until workflow_instance_id is set by the thread
+            # while workflow_instance_id is None:
+            #    sleep(0.1)
+
+            stream_name = f"{workflow_instance_id}_output"
+            consumer_name = f"{workflow_instance_id}_agent"  # consumer name
+            group_name = "omappagent"  # replace with your consumer group name
+            poll_interval = 1
+            print (f"self._input_prompt: {self._input_prompt}")
+            if self._input_prompt:
+                print (f"self._input_prompt: {self._input_prompt}")
+                self.first_input(
+                    workflow_instance_id=workflow_instance_id,
+                    input_prompt=self._input_prompt,
+                )
+
+            try:
+                container.get_connector("redis_stream_client")._client.xgroup_create(
+                    stream_name, group_name, id="0", mkstream=True
+                )
+                print (f"stream_name: {stream_name}, group_name: {group_name}")
+            except Exception as e:
+                logging.debug(f"Consumer group may already exist: {e}")
+
+            while True:
+                try:
+                    # check exception queue
+                    if not exception_queue.empty():
+                        exc = exception_queue.get()
+                        raise RuntimeError(f"Workflow thread failed: {exc}") from exc
+
+                    status = self._interactor.get_workflow(workflow_id=workflow_instance_id).status
+                    if status in terminal_status:
+                        break
+
+                    data_flag = False
+                    content = None
+
+                    workflow_status = workflow_client.get_workflow_status(workflow_instance_id)
+                    if workflow_status.status not in running_status:
+                        logging.info(f"Workflow {workflow_instance_id} is not running, exiting...")
+                        break
+
+                    # Read new messages from consumer group
+                    messages = container.get_connector("redis_stream_client")._client.xreadgroup(
+                        group_name, consumer_name, {stream_name: ">"}, count=1
+                    )
+                    # Convert byte data to string
+                    messages = [
+                        (
+                            stream,
+                            [
+                                (
+                                    message_id,
+                                    {
+                                        k.decode("utf-8"): v.decode("utf-8")
+                                        for k, v in message.items()
+                                    },
+                                )
+                                for message_id, message in message_list
+                            ],
+                        )
+                        for stream, message_list in messages
+                    ]
+
+                    for stream, message_list in messages:
+                        for message_id, message in message_list:
+                            data_flag, content = self.process_message(message)
+                            # confirm message has been processed
+                            container.get_connector("redis_stream_client")._client.xack(
+                                stream_name, group_name, message_id
+                            )
+
+                    # If a message requests user input, gather it from stdin
+                    if data_flag:
+                        contents = []
+                        while True:
+                            print(
+                                f"{Fore.GREEN}{content}(Waiting for input. Your input can only be text or image path each time, you can press Enter once to input multiple times. Press Enter twice to finish the entire input.):{Style.RESET_ALL}"
+                            )
+                            user_input_lines = []
+                            while True:
+                                line = input(f"{Fore.GREEN}>>>{Style.RESET_ALL}")
+                                if line == "":
+                                    break
+                                user_input_lines.append(line)
+                            logging.info(f"User input lines: {user_input_lines}")
+
+                            for user_input in user_input_lines:
+                                if self.is_url(user_input) or self.is_file(user_input):
+                                    contents.append({"type": "image_url", "data": user_input})
+                                else:
+                                    contents.append({"type": "text", "data": user_input})
+                            if len(contents) > 0:
+                                break
+
+                        result = {
+                            "agent_id": workflow_instance_id,
+                            "messages": [{"role": "user", "content": contents}],
+                            "kwargs": {},
+                        }
+                        print (f"result: {result}")
+                        container.get_connector("redis_stream_client")._client.xadd(
+                            f"{workflow_instance_id}_input",
+                            {"payload": json.dumps(result, ensure_ascii=False)},
+                        )
+
+                    sleep(poll_interval)
+
+                except Exception as e:
+                    logging.error(f"Error in main loop: {e}")
+                    sys.exit(1)
+            self.stop_interactor()
+            
+
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if workflow_instance_id is not None:
+                self._interactor._executor.terminate(workflow_id=workflow_instance_id)
+            raise
+
+    def stop_interactor(self):
+        #self._task_handler_interactor.stop_processes()
+        print ("stop_interactor")
+        sys.exit(0)
+
+
+    def start_processor(self):
+        workflow_instance_id = None
+        try:
+            worker_config = build_from_file(self._config_path)
+            self._task_handler_processor = TaskHandler(
+                worker_config=worker_config, workers=self._workers, task_to_domain=self._task_to_domain
+            )
+            self._task_handler_processor.start_processes()
+            workflow_instance_id = self._processor.start_workflow_with_input(
+                workflow_input={}, task_to_domain=self._task_to_domain
+            )
+            user_input = input(
+                f"{Fore.GREEN}Please input a folder path of images:(WaitPress Enter to finish the entire input.):\n>>>{Style.RESET_ALL}"
+            )
+
+            image_items = []
+            idx = 0
+            for image_file in Path(user_input).iterdir():
+                if image_file.is_file() and image_file.suffix in [
+                    ".png",
+                    ".jpg",
+                    ".jpeg",
+                ]:
+                    image_items.append(
+                        {
+                            "type": "image_url",
+                            "resource_id": str(idx),
+                            "data": str(image_file),
+                        }
+                    )
+                    idx += 1
+            result = {"content": image_items}
+            container.get_connector("redis_stream_client")._client.xadd(
+                f"image_process", {"payload": json.dumps(result, ensure_ascii=False)}
+            )
+            while True:
+                status = self._processor.get_workflow(
+                    workflow_id=workflow_instance_id
+                ).status
+                if status in terminal_status:
+                    break
+
+                sleep(1)
+            self.stop_processor()
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if workflow_instance_id is not None:
+                self._processor._executor.terminate(workflow_id=workflow_instance_id)
+            raise
+
+    def stop_processor(self):
+        self._task_handler_processor.stop_processes()
+
+    def process_message(self, message):
+        logging.info(f"Received message: {message}")
+        try:
+            payload = message.get("payload")
+            # check payload data
+            if not payload:
+                logging.error("Payload is empty")
+                return False, None
+
+            try:
+                payload_data = json.loads(payload)
+            except json.JSONDecodeError as e:
+                logging.error(f"Payload is not a valid JSON: {e}")
+                return False, None
+
+            if (
+                "interaction_type" in payload_data
+                and payload_data["interaction_type"] == 1
+            ):
+                content = payload_data["message"]["content"]
+                return True, content
+
+        except Exception as e:
+            logging.error(f"Error processing message: {e}")
+            return False, None
+        return False, None
+
+    def is_file(self, path: str) -> bool:
+        """
+        Determine if the given string is a file
+
+        :param path: File path string
+        :return: Returns True if it is a file, otherwise returns False
+        """
+        import os
+
+        try:
+            return os.path.isfile(path)
+        except Exception as e:
+            logging.error(f"Error checking if path is a file: {e}")
+            return False
+
+    def is_url(self, url: str) -> bool:
+        """
+        Determine if the given string is a URL
+        """
+        import re
+
+        return bool(re.match(r"^https?://", url))
+
+    def first_input(self, workflow_instance_id: str, input_prompt=""):
+        contents = []
+        while True:
+            print(
+                f"{Fore.GREEN}{input_prompt}(Waiting for input. Your input can only be text or image path each time, you can press Enter once to input multiple times. Press Enter twice to finish the entire input.):{Style.RESET_ALL}"
+            )
+            user_input_lines = []
+            while True:
+                line = input(f"{Fore.GREEN}>>>{Style.RESET_ALL}")
+                if line == "":
+                    break
+                user_input_lines.append(line)
+            logging.info(f"User input lines: {user_input_lines}")
+
+            for user_input in user_input_lines:
+                if self.is_url(user_input) or self.is_file(user_input):
+                    contents.append({"type": "image_url", "data": user_input})
+                else:
+                    contents.append({"type": "text", "data": user_input})
+            if len(contents) > 0:
+                break
+        result = {
+            "agent_id": workflow_instance_id,
+            "messages": [{"role": "user", "content": contents}],
+            "kwargs": {},
+        }
+        container.get_connector("redis_stream_client")._client.xadd(
+            f"{workflow_instance_id}_input",
+            {"payload": json.dumps(result, ensure_ascii=False)},
+        )
diff --git a/omagent_core/clients/devices/lite_version/cli.py b/omagent_core/clients/devices/lite_version/cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..b657a2a7d2fcf6b863ddc90504e238b661a1322c
--- /dev/null
+++ b/omagent_core/clients/devices/lite_version/cli.py
@@ -0,0 +1,71 @@
+
+from omagent_core.utils.container import container
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.registry import registry
+from omagent_core.clients.devices.cli.callback import DefaultCallback
+from colorama import Fore
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+
+registry.import_module()
+
+container.register_callback(callback=DefaultCallback)
+
+class DefaultClient:
+    def __init__(
+        self,
+        interactor: ConductorWorkflow = None,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = [],
+        input_prompt: str = None
+    ) -> None:
+        self._interactor = interactor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+        self._input_prompt = input_prompt        
+        worker_config = build_from_file(self._config_path)
+        self.initialization(workers, worker_config)
+
+    def initialization(self, workers, worker_config):        
+        self.workers = {}
+        for worker in workers:
+            self.workers[type(worker).__name__] = worker            
+        
+        for config in worker_config:
+            worker_cls = registry.get_worker(config['name'])        
+            self.workers[config['name']] = worker_cls(**config)                    
+
+    def start_processor_with_input(self, workflow_input: dict):                          
+        self._interactor.start_workflow_with_input(workflow_input=workflow_input, workers=self.workers)
+
+    def start_interactor(self):
+        """
+        This function takes keyboard input from the user and runs the workflow 
+        using the `start_workflow_with_input` method.
+        """
+        while True:
+            try:
+                # Prompt the user for input
+                user_input = input(f"\n{Fore.GREEN}Enter your query (or type 'exit' to quit): ")
+
+                # Exit the interaction loop if the user types 'exit'
+                if user_input.lower() == "exit":
+                    print("Exiting the interaction. Goodbye!")
+                    break
+
+                # Prepare the workflow input as a dictionary
+                workflow_input = {"query": user_input}
+
+                # Pass the input to start_workflow_with_input
+                self.start_processor_with_input(workflow_input)
+            
+            except KeyboardInterrupt:
+                print("\nInteraction interrupted. Goodbye!")
+                break
+            except Exception as e:
+                print(f"An error occurred: {e}")
+    
+        
\ No newline at end of file
diff --git a/omagent_core/clients/devices/programmatic/__init__.py b/omagent_core/clients/devices/programmatic/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..656ca6969241434412cac77cfac759c7333ea107
--- /dev/null
+++ b/omagent_core/clients/devices/programmatic/__init__.py
@@ -0,0 +1,11 @@
+import os
+
+env = os.getenv("OMAGENT_MODE", "lite").lower()
+
+if env == "lite":
+    print ("importing lite client") 
+    from .lite_client import ProgrammaticClient
+else:
+    print ("importing pro client")
+    from .client import ProgrammaticClient
+
diff --git a/omagent_core/clients/devices/programmatic/client.py b/omagent_core/clients/devices/programmatic/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..6ad7f352f8dca95b23c46648ba3e91e031745355
--- /dev/null
+++ b/omagent_core/clients/devices/programmatic/client.py
@@ -0,0 +1,113 @@
+import multiprocessing
+from time import sleep
+
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.http.models.workflow_status import terminal_status
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+registry.import_module()
+
+
+class ProgrammaticClient:
+    def __init__(
+        self,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = []
+    ) -> None:
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+        self._task_handler_processor = None
+        self._task_to_domain = {}
+
+    def start_processor(self):
+        worker_config = build_from_file(self._config_path)
+        self._task_handler_processor = TaskHandler(
+            worker_config=worker_config, workers=self._workers
+        )
+        self._task_handler_processor.start_processes()
+        self._processor.start_workflow_with_input(workflow_input={}, task_to_domain=self._task_to_domain)
+
+    def start_processor_with_input(self, workflow_input: dict):
+        try:
+            if self._task_handler_processor is None:
+                worker_config = build_from_file(self._config_path)
+                self._task_handler_processor = TaskHandler(
+                    worker_config=worker_config, workers=self._workers, task_to_domain=self._task_to_domain
+                )
+                self._task_handler_processor.start_processes()
+            return self._process_workflow(self._processor, workflow_input)
+        except Exception as e:
+            logging.error(f"Error in start_processor_with_input: {e}")
+
+    def start_batch_processor(self, workflow_input_list: list[dict], max_tasks: int = 10):
+        results = [None] * len(workflow_input_list)
+        worker_config = build_from_file(self._config_path)
+        if self._task_handler_processor is None:
+            self._task_handler_processor = TaskHandler(worker_config=worker_config, workers=self._workers, task_to_domain=self._task_to_domain)
+            self._task_handler_processor.start_processes()
+        
+        result_queue = multiprocessing.Queue()
+        active_processes = []
+        
+        for idx, workflow_input in enumerate(workflow_input_list):
+            while len(active_processes) >= max_tasks:
+                for p in active_processes[:]:
+                    if not p.is_alive():
+                        p.join()
+                        active_processes.remove(p)
+                        if not result_queue.empty():
+                            task_idx, result = result_queue.get()
+                            results[task_idx] = result
+                sleep(0.1)
+            
+            p = multiprocessing.Process(
+                target=self._process_workflow_with_queue, 
+                args=(self._processor, workflow_input, result_queue, idx)
+            )
+            p.start()
+            active_processes.append(p)
+
+        for p in active_processes:
+            p.join()
+        
+        while not result_queue.empty():
+            task_idx, result = result_queue.get()
+            results[task_idx] = result
+            
+        return results
+
+    def stop_processor(self):
+        if self._task_handler_processor is not None:
+            self._task_handler_processor.stop_processes()
+
+    def _process_workflow(self, workflow: ConductorWorkflow, workflow_input: dict):
+        workflow_instance_id = None
+        try:
+            workflow_instance_id = workflow.start_workflow_with_input(
+                workflow_input=workflow_input, task_to_domain=self._task_to_domain
+            )
+            while True:
+                status = workflow.get_workflow(workflow_id=workflow_instance_id).status
+                if status in terminal_status:
+                    break
+                sleep(1)
+            return workflow.get_workflow(workflow_id=workflow_instance_id).output
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if workflow_instance_id is not None:
+                workflow._executor.terminate(workflow_id=workflow_instance_id)
+            raise  # Rethrow the exception to allow the program to exit normally
+
+    def _process_workflow_with_queue(self, workflow: ConductorWorkflow, workflow_input: dict, 
+                                   queue: multiprocessing.Queue, task_idx: int):
+        try:
+            result = self._process_workflow(workflow, workflow_input)
+            queue.put((task_idx, result))
+        except Exception as e:
+            logging.error(f"Error in process workflow: {e}")
+            queue.put((task_idx, None))
diff --git a/omagent_core/clients/devices/programmatic/lite_client.py b/omagent_core/clients/devices/programmatic/lite_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..59222478f42b0eb5e0d0c4ffbee759fb2af5bcdf
--- /dev/null
+++ b/omagent_core/clients/devices/programmatic/lite_client.py
@@ -0,0 +1,56 @@
+
+from omagent_core.utils.container import container
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.registry import registry
+from omagent_core.clients.devices.cli.callback import DefaultCallback
+from colorama import Fore
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.registry import registry
+        
+registry.import_module()
+
+container.register_callback(callback=DefaultCallback)
+
+class ProgrammaticClient:
+    def __init__(
+        self,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = [],
+        input_prompt: str = None
+    ) -> None:
+        self._interactor = processor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+        self._input_prompt = input_prompt        
+        worker_config = build_from_file(self._config_path)
+        self.initialization(workers, worker_config)
+
+    def initialization(self, workers, worker_config):        
+        self.workers = {}
+        for worker in workers:
+            self.workers[type(worker).__name__] = worker            
+        
+        for config in worker_config:
+            worker_cls = registry.get_worker(config['name'])        
+            self.workers[config['name']] = worker_cls(**config)                    
+
+    def start_processor_with_input(self, workflow_input: dict):                          
+        self._interactor.start_workflow_with_input(workflow_input=workflow_input, workers=self.workers)
+
+    
+    def start_batch_processor(self, workflow_input_list: list, max_tasks=1):
+        results = []
+        for workflow_input in workflow_input_list:
+            print ("workflow_input:",workflow_input)
+            result = self._interactor.start_workflow_with_input(workflow_input=workflow_input, workers=self.workers)
+            print ("result:",result)
+            results.append(result)
+        return results
+    
+    def stop_processor(self):
+        print ("stop_processor")
\ No newline at end of file
diff --git a/omagent_core/clients/devices/webpage/client.py b/omagent_core/clients/devices/webpage/client.py
new file mode 100644
index 0000000000000000000000000000000000000000..ef49bccb2694f3ea0cfc815b3de79f3354d17b84
--- /dev/null
+++ b/omagent_core/clients/devices/webpage/client.py
@@ -0,0 +1,370 @@
+import html
+import json
+from time import sleep
+
+import gradio as gr
+from omagent_core.clients.devices.app.callback import AppCallback
+from omagent_core.clients.devices.app.input import AppInput
+from omagent_core.clients.devices.app.schemas import ContentStatus, MessageType
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.http.models.workflow_status import terminal_status
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+registry.import_module()
+
+container.register_connector(name="redis_stream_client", connector=RedisConnector)
+# container.register_stm(stm='RedisSTM')
+container.register_callback(callback=AppCallback)
+container.register_input(input=AppInput)
+
+
+class WebpageClient:
+    def __init__(
+        self,
+        interactor: ConductorWorkflow = None,
+        processor: ConductorWorkflow = None,
+        config_path: str = "./config",
+        workers: list = [],
+    ) -> None:
+        self._interactor = interactor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+        self._workflow_instance_id = None
+        self._worker_config = build_from_file(self._config_path)
+        self._task_to_domain = {}
+        self._incomplete_message = ""
+        self._custom_css = """
+            #OmAgent {
+                height: 100vh !important;
+                max-height: calc(100vh - 190px) !important;
+                overflow-y: auto;
+            }
+            
+            .running-message {
+                margin: 0;
+                padding: 2px 4px;
+                white-space: pre-wrap;
+                word-wrap: break-word;
+                font-family: inherit;
+            }
+            
+            /* Remove the background and border of the message box */
+            .message-wrap {
+                background: none !important;
+                border: none !important;
+                padding: 0 !important;
+                margin: 0 !important;
+            }
+            
+            /* Remove the bubble style of the running message */
+            .message:has(.running-message) {
+                background: none !important;
+                border: none !important;
+                padding: 0 !important;
+                box-shadow: none !important;
+            }
+        """
+
+    def start_interactor(self):
+        self._task_handler_interactor = TaskHandler(
+            worker_config=self._worker_config, workers=self._workers, task_to_domain=self._task_to_domain
+        )
+        self._task_handler_interactor.start_processes()
+        try:
+            with gr.Blocks(title="OmAgent", css=self._custom_css) as chat_interface:
+                chatbot = gr.Chatbot(
+                    elem_id="OmAgent",
+                    bubble_full_width=False,
+                    type="messages",
+                    height="100%",
+                )
+
+                chat_input = gr.MultimodalTextbox(
+                    interactive=True,
+                    file_count="multiple",
+                    placeholder="Enter message or upload file...",
+                    show_label=False,
+                )
+
+                chat_msg = chat_input.submit(
+                    self.add_message, [chatbot, chat_input], [chatbot, chat_input]
+                )
+                bot_msg = chat_msg.then(
+                    self.bot, chatbot, chatbot, api_name="bot_response"
+                )
+                bot_msg.then(
+                    lambda: gr.MultimodalTextbox(interactive=True), None, [chat_input]
+                )
+            chat_interface.launch()
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if self._workflow_instance_id is not None:
+                self._interactor._executor.terminate(
+                    workflow_id=self._workflow_instance_id
+                )
+            raise
+
+    def stop_interactor(self):
+        self._task_handler_interactor.stop_processes()
+
+    def start_processor(self):
+        self._task_handler_processor = TaskHandler(
+            worker_config=self._worker_config, workers=self._workers, task_to_domain=self._task_to_domain
+        )
+        self._task_handler_processor.start_processes()
+
+        try:
+            with gr.Blocks(title="OmAgent", css=self._custom_css) as chat_interface:
+                chatbot = gr.Chatbot(
+                    elem_id="OmAgent",
+                    bubble_full_width=False,
+                    type="messages",
+                    height="100%",
+                )
+
+                chat_input = gr.MultimodalTextbox(
+                    interactive=True,
+                    file_count="multiple",
+                    placeholder="Enter message or upload file...",
+                    show_label=False,
+                )
+
+                chat_msg = chat_input.submit(
+                    self.add_processor_message,
+                    [chatbot, chat_input],
+                    [chatbot, chat_input],
+                )
+                bot_msg = chat_msg.then(
+                    self.processor_bot, chatbot, chatbot, api_name="bot_response"
+                )
+                bot_msg.then(
+                    lambda: gr.MultimodalTextbox(interactive=True), None, [chat_input]
+                )
+            chat_interface.launch(server_port=7861)
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if self._workflow_instance_id is not None:
+                self._processor._executor.terminate(
+                    workflow_id=self._workflow_instance_id
+                )
+            raise
+
+    def stop_processor(self):
+        self._task_handler_processor.stop_processes()
+
+    def add_message(self, history, message):
+        if self._workflow_instance_id is None:
+            self._workflow_instance_id = self._interactor.start_workflow_with_input(
+                workflow_input={}, task_to_domain=self._task_to_domain
+            )
+        contents = []
+        for x in message["files"]:
+            history.append({"role": "user", "content": {"path": x}})
+            contents.append({"type": "image_url", "data": x})
+        if message["text"] is not None:
+            history.append({"role": "user", "content": message["text"]})
+            contents.append({"type": "text", "data": message["text"]})
+        result = {
+            "agent_id": self._workflow_instance_id,
+            "messages": [{"role": "user", "content": contents}],
+            "kwargs": {},
+        }
+        container.get_connector("redis_stream_client")._client.xadd(
+            f"{self._workflow_instance_id}_input",
+            {"payload": json.dumps(result, ensure_ascii=False)},
+        )
+        return history, gr.MultimodalTextbox(value=None, interactive=False)
+
+    def add_processor_message(self, history, message):
+        if self._workflow_instance_id is None:
+            self._workflow_instance_id = self._processor.start_workflow_with_input(
+                workflow_input={}, task_to_domain=self._task_to_domain
+            )
+        image_items = []
+        for idx, x in enumerate(message["files"]):
+            history.append({"role": "user", "content": {"path": x}})
+            image_items.append(
+                {"type": "image_url", "resource_id": str(idx), "data": str(x)}
+            )
+        result = {"content": image_items}
+        container.get_connector("redis_stream_client")._client.xadd(
+            f"image_process", {"payload": json.dumps(result, ensure_ascii=False)}
+        )
+        return history, gr.MultimodalTextbox(value=None, interactive=False)
+
+    def bot(self, history: list):
+        stream_name = f"{self._workflow_instance_id}_output"
+        consumer_name = f"{self._workflow_instance_id}_agent"  # consumer name
+        group_name = "omappagent"  # replace with your consumer group name
+        running_stream_name = f"{self._workflow_instance_id}_running"
+        self._check_redis_stream_exist(stream_name, group_name)
+        self._check_redis_stream_exist(running_stream_name, group_name)
+        while True:
+            # read running stream
+            running_messages = self._get_redis_stream_message(
+                group_name, consumer_name, running_stream_name
+            )
+            for stream, message_list in running_messages:
+                for message_id, message in message_list:
+                    payload_data = self._get_message_payload(message)
+                    if payload_data is None:
+                        continue
+                    progress = html.escape(payload_data.get("progress", ""))
+                    message = html.escape(payload_data.get("message", ""))
+                    formatted_message = (
+                        f'<pre class="running-message">{progress}: {message}</pre>'
+                    )
+                    history.append({"role": "assistant", "content": formatted_message})
+                    yield history
+
+                    container.get_connector("redis_stream_client")._client.xack(
+                        running_stream_name, group_name, message_id
+                    )
+            # read output stream
+            messages = self._get_redis_stream_message(
+                group_name, consumer_name, stream_name
+            )
+            finish_flag = False
+
+            for stream, message_list in messages:
+                for message_id, message in message_list:
+                    incomplete_flag = False
+                    payload_data = self._get_message_payload(message)
+                    if payload_data is None:
+                        continue
+                    if payload_data["content_status"] == ContentStatus.INCOMPLETE.value:
+                        incomplete_flag = True
+                    message_item = payload_data["message"]
+                    if message_item["type"] == MessageType.IMAGE_URL.value:
+                        history.append(
+                            {
+                                "role": "assistant",
+                                "content": {"path": message_item["content"]},
+                            }
+                        )
+                    else:
+                        if incomplete_flag:
+                            self._incomplete_message = (
+                                self._incomplete_message + message_item["content"]
+                            )
+                            if history and history[-1]["role"] == "assistant":
+                                history[-1]["content"] = self._incomplete_message
+                            else:
+                                history.append(
+                                    {
+                                        "role": "assistant",
+                                        "content": self._incomplete_message,
+                                    }
+                                )
+                        else:
+                            if self._incomplete_message != "":
+                                self._incomplete_message = (
+                                    self._incomplete_message + message_item["content"]
+                                )
+                                if history and history[-1]["role"] == "assistant":
+                                    history[-1]["content"] = self._incomplete_message
+                                else:
+                                    history.append(
+                                        {
+                                            "role": "assistant",
+                                            "content": self._incomplete_message,
+                                        }
+                                    )
+                                self._incomplete_message = ""
+                            else:
+                                history.append(
+                                    {
+                                        "role": "assistant",
+                                        "content": message_item["content"],
+                                    }
+                                )
+
+                    yield history
+
+                    container.get_connector("redis_stream_client")._client.xack(
+                        stream_name, group_name, message_id
+                    )
+
+                    # check finish flag
+                    if (
+                        "interaction_type" in payload_data
+                        and payload_data["interaction_type"] == 1
+                    ):
+                        finish_flag = True
+                    if (
+                        "content_status" in payload_data
+                        and payload_data["content_status"]
+                        == ContentStatus.END_ANSWER.value
+                    ):
+                        self._workflow_instance_id = None
+                        finish_flag = True
+
+            if finish_flag:
+                break
+            sleep(0.01)
+
+    def processor_bot(self, history: list):
+        history.append({"role": "assistant", "content": f"processing..."})
+        yield history
+        while True:
+            status = self._processor.get_workflow(
+                workflow_id=self._workflow_instance_id
+            ).status
+            if status in terminal_status:
+                history.append({"role": "assistant", "content": f"completed"})
+                yield history
+                self._workflow_instance_id = None
+                break
+            sleep(0.01)
+
+    def _get_redis_stream_message(
+        self, group_name: str, consumer_name: str, stream_name: str
+    ):
+        messages = container.get_connector("redis_stream_client")._client.xreadgroup(
+            group_name, consumer_name, {stream_name: ">"}, count=1
+        )
+        messages = [
+            (
+                stream,
+                [
+                    (
+                        message_id,
+                        {
+                            k.decode("utf-8"): v.decode("utf-8")
+                            for k, v in message.items()
+                        },
+                    )
+                    for message_id, message in message_list
+                ],
+            )
+            for stream, message_list in messages
+        ]
+        return messages
+
+    def _check_redis_stream_exist(self, stream_name: str, group_name: str):
+        try:
+            container.get_connector("redis_stream_client")._client.xgroup_create(
+                stream_name, group_name, id="0", mkstream=True
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+
+    def _get_message_payload(self, message: dict):
+        logging.info(f"Received running message: {message}")
+        payload = message.get("payload")
+        # check payload data
+        if not payload:
+            logging.error("Payload is empty")
+            return None
+        try:
+            payload_data = json.loads(payload)
+        except json.JSONDecodeError as e:
+            logging.error(f"Payload is not a valid JSON: {e}")
+            return None
+        return payload_data
diff --git a/omagent_core/clients/input_base.py b/omagent_core/clients/input_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..119db89eaaff13eb9b2a19ab1e67f55a34734346
--- /dev/null
+++ b/omagent_core/clients/input_base.py
@@ -0,0 +1,16 @@
+from abc import ABC, abstractmethod
+
+from omagent_core.base import BotBase
+
+
+class InputBase(BotBase, ABC):
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        arbitrary_types_allowed = True
+        extra = "allow"
+
+    @abstractmethod
+    def read_input(self, **kwargs):
+        pass
diff --git a/omagent_core/engine/__init__.py b/omagent_core/engine/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/authorization_client.py b/omagent_core/engine/authorization_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..dafdc3d3711b1d851c8f282cf0f2d117e8203bea
--- /dev/null
+++ b/omagent_core/engine/authorization_client.py
@@ -0,0 +1,164 @@
+from abc import ABC, abstractmethod
+from typing import Dict, List, Optional
+
+from omagent_core.engine.http.models.conductor_application import \
+    ConductorApplication
+from omagent_core.engine.http.models.conductor_user import ConductorUser
+from omagent_core.engine.http.models.create_or_update_application_request import \
+    CreateOrUpdateApplicationRequest
+from omagent_core.engine.http.models.group import Group
+from omagent_core.engine.http.models.subject_ref import SubjectRef
+from omagent_core.engine.http.models.target_ref import TargetRef
+from omagent_core.engine.http.models.upsert_group_request import \
+    UpsertGroupRequest
+from omagent_core.engine.http.models.upsert_user_request import \
+    UpsertUserRequest
+from omagent_core.engine.orkes.models.access_key import AccessKey
+from omagent_core.engine.orkes.models.access_type import AccessType
+from omagent_core.engine.orkes.models.created_access_key import \
+    CreatedAccessKey
+from omagent_core.engine.orkes.models.granted_permission import \
+    GrantedPermission
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+
+
+class AuthorizationClient(ABC):
+    # Applications
+    @abstractmethod
+    def create_application(
+        self, create_or_update_application_request: CreateOrUpdateApplicationRequest
+    ) -> ConductorApplication:
+        pass
+
+    @abstractmethod
+    def get_application(self, application_id: str) -> ConductorApplication:
+        pass
+
+    @abstractmethod
+    def list_applications(self) -> List[ConductorApplication]:
+        pass
+
+    @abstractmethod
+    def update_application(
+        self,
+        create_or_update_application_request: CreateOrUpdateApplicationRequest,
+        application_id: str,
+    ) -> ConductorApplication:
+        pass
+
+    @abstractmethod
+    def delete_application(self, application_id: str):
+        pass
+
+    @abstractmethod
+    def add_role_to_application_user(self, application_id: str, role: str):
+        pass
+
+    @abstractmethod
+    def remove_role_from_application_user(self, application_id: str, role: str):
+        pass
+
+    @abstractmethod
+    def set_application_tags(self, tags: List[MetadataTag], application_id: str):
+        pass
+
+    @abstractmethod
+    def get_application_tags(self, application_id: str) -> List[MetadataTag]:
+        pass
+
+    @abstractmethod
+    def delete_application_tags(self, tags: List[MetadataTag], application_id: str):
+        pass
+
+    @abstractmethod
+    def create_access_key(self, application_id: str) -> CreatedAccessKey:
+        pass
+
+    @abstractmethod
+    def get_access_keys(self, application_id: str) -> List[AccessKey]:
+        pass
+
+    @abstractmethod
+    def toggle_access_key_status(self, application_id: str, key_id: str) -> AccessKey:
+        pass
+
+    @abstractmethod
+    def delete_access_key(self, application_id: str, key_id: str):
+        pass
+
+    # Users
+    @abstractmethod
+    def upsert_user(
+        self, upsert_user_request: UpsertUserRequest, user_id: str
+    ) -> ConductorUser:
+        pass
+
+    @abstractmethod
+    def get_user(self, user_id: str) -> ConductorUser:
+        pass
+
+    @abstractmethod
+    def list_users(self, apps: Optional[bool] = False) -> List[ConductorUser]:
+        pass
+
+    @abstractmethod
+    def delete_user(self, user_id: str):
+        pass
+
+    # Groups
+    @abstractmethod
+    def upsert_group(
+        self, upsert_group_request: UpsertGroupRequest, group_id: str
+    ) -> Group:
+        pass
+
+    @abstractmethod
+    def get_group(self, group_id: str) -> Group:
+        pass
+
+    @abstractmethod
+    def list_groups(self) -> List[Group]:
+        pass
+
+    @abstractmethod
+    def delete_group(self, group_id: str):
+        pass
+
+    @abstractmethod
+    def add_user_to_group(self, group_id: str, user_id: str):
+        pass
+
+    @abstractmethod
+    def get_users_in_group(self, group_id: str) -> List[ConductorUser]:
+        pass
+
+    @abstractmethod
+    def remove_user_from_group(self, group_id: str, user_id: str):
+        pass
+
+    # Permissions
+    @abstractmethod
+    def grant_permissions(
+        self, subject: SubjectRef, target: TargetRef, access: List[AccessType]
+    ):
+        pass
+
+    @abstractmethod
+    def get_permissions(self, target: TargetRef) -> Dict[str, List[SubjectRef]]:
+        pass
+
+    @abstractmethod
+    def get_granted_permissions_for_group(
+        self, group_id: str
+    ) -> List[GrantedPermission]:
+        pass
+
+    @abstractmethod
+    def get_granted_permissions_for_user(self, user_id: str) -> List[GrantedPermission]:
+        pass
+
+    @abstractmethod
+    def remove_permissions(
+        self, subject: SubjectRef, target: TargetRef, access: List[AccessType]
+    ):
+        pass
diff --git a/omagent_core/engine/automator/__init__.py b/omagent_core/engine/automator/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/automator/task_handler.py b/omagent_core/engine/automator/task_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..ae6b13757c25c50d302c498c6c1dd80b7e4721df
--- /dev/null
+++ b/omagent_core/engine/automator/task_handler.py
@@ -0,0 +1,250 @@
+import importlib
+import logging
+import os
+from copy import deepcopy
+from multiprocessing import (Process, Queue, freeze_support, get_context,
+                             set_start_method)
+from sys import platform
+from typing import List
+
+from omagent_core.engine.automator.task_runner import TaskRunner
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.configuration.settings.metrics_settings import \
+    MetricsSettings
+from omagent_core.engine.telemetry.metrics_collector import MetricsCollector
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.utils.container import container
+from omagent_core.utils.registry import registry
+
+logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+_decorated_functions = {}
+_mp_fork_set = False
+if not _mp_fork_set:
+    try:
+        if platform == "win32":
+            set_start_method("spawn")
+        else:
+            set_start_method("fork")
+        _mp_fork_set = True
+    except Exception as e:
+        logger.info(
+            f"error when setting multiprocessing.set_start_method - maybe the context is set {e.args}"
+        )
+    if platform == "darwin":
+        os.environ["no_proxy"] = "*"
+
+
+def register_decorated_fn(
+    name: str, poll_interval: int, domain: str, worker_id: str, func
+):
+    logger.info(f"decorated {name}")
+    _decorated_functions[(name, domain)] = {
+        "func": func,
+        "poll_interval": poll_interval,
+        "domain": domain,
+        "worker_id": worker_id,
+    }
+
+
+class TaskHandler:
+    def __init__(
+        self,
+        worker_config: List = [],
+        workers: List[BaseWorker] = [],
+        metrics_settings: MetricsSettings = None,
+        import_modules: List[str] = None,
+        task_to_domain: dict[str, str] = None,
+    ):
+        """Initialize a new TaskHandler instance.
+
+        Args:
+            worker_config (List): List of worker configurations. Each config should be a dict containing
+                worker name, optional concurrency and other settings. The Worker replicates and starts the corresponding number of processes based on the concurrency parameter
+            workers (List[BaseWorker]): List of pre-configured worker instances. Instances of these workers are deepcopied based on the concurrency setting.If your worker instances contain objects that cannot be deepcopied, set the instance's concurrency property to 1 and actively expand the concurrency count in the workers list.
+            metrics_settings (MetricsSettings, optional): Configuration for metrics collection.
+                If None, metrics collection will be disabled.
+            import_modules (List[str], optional): List of module paths to import during initialization.
+        """
+
+        self.logger_process, self.queue = _setup_logging_queue(
+            container.conductor_config
+        )
+
+        # imports
+        importlib.import_module("omagent_core.engine.http.models.task")
+        if import_modules is not None:
+            for module in import_modules:
+                logger.info(f"loading module {module}")
+                importlib.import_module(module)
+
+        existing_workers = []
+        for worker in workers:
+            concurrency = getattr(
+                worker, "concurrency", BaseWorker.model_fields["concurrency"].default
+            )
+            if concurrency > 1:
+                existing_workers.extend(
+                    [deepcopy(worker) for _ in range(concurrency - 1)]
+                )
+        workers.extend(existing_workers)
+
+        for config in worker_config:
+            worker_cls = registry.get_worker(config["name"])
+            concurrency = config.get(
+                "concurrency", BaseWorker.model_fields["concurrency"].default
+            )
+            workers.extend([worker_cls(**config) for _ in range(concurrency)])
+        for worker in workers:
+            if task_to_domain is not None:
+                task_to_domain[worker.task_definition_name] = worker.domain
+        self.__create_task_runner_processes(
+            workers, container.conductor_config, metrics_settings
+        )
+        self.__create_metrics_provider_process(metrics_settings)
+        logger.info("TaskHandler initialized")
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.stop_processes()
+
+    def stop_processes(self) -> None:
+        self.__stop_task_runner_processes()
+        self.__stop_metrics_provider_process()
+        logger.info("Stopped worker processes...")
+        self.queue.put(None)
+        self.logger_process.terminate()
+
+    def start_processes(self) -> None:
+        logger.info("Starting worker processes...")
+        freeze_support()
+        self.__start_task_runner_processes()
+        self.__start_metrics_provider_process()
+        logger.info("Started all processes")
+
+    def join_processes(self) -> None:
+        try:
+            self.__join_task_runner_processes()
+            self.__join_metrics_provider_process()
+            logger.info("Joined all processes")
+        except KeyboardInterrupt:
+            logger.info("KeyboardInterrupt: Stopping all processes")
+            self.stop_processes()
+
+    def __create_metrics_provider_process(
+        self, metrics_settings: MetricsSettings
+    ) -> None:
+        if metrics_settings is None:
+            self.metrics_provider_process = None
+            return
+        self.metrics_provider_process = Process(
+            target=MetricsCollector.provide_metrics, args=(metrics_settings,)
+        )
+        logger.info("Created MetricsProvider process")
+
+    def __create_task_runner_processes(
+        self,
+        workers: List[BaseWorker],
+        configuration: Configuration,
+        metrics_settings: MetricsSettings,
+    ) -> None:
+        self.task_runner_processes = []
+        for worker in workers:
+            self.__create_task_runner_process(worker, configuration, metrics_settings)
+
+    def __create_task_runner_process(
+        self,
+        worker: BaseWorker,
+        configuration: Configuration,
+        metrics_settings: MetricsSettings,
+    ) -> None:
+        task_runner = TaskRunner(worker, configuration, metrics_settings)
+        process = Process(target=task_runner.run)
+        self.task_runner_processes.append(process)
+
+    def __start_metrics_provider_process(self):
+        if self.metrics_provider_process is None:
+            return
+        self.metrics_provider_process.start()
+        logger.info("Started MetricsProvider process")
+
+    def __start_task_runner_processes(self):
+        n = 0
+        for task_runner_process in self.task_runner_processes:
+            task_runner_process.start()
+            n = n + 1
+        logger.info(f"Started {n} TaskRunner process")
+
+    def __join_metrics_provider_process(self):
+        if self.metrics_provider_process is None:
+            return
+        self.metrics_provider_process.join()
+        logger.info("Joined MetricsProvider processes")
+
+    def __join_task_runner_processes(self):
+        for task_runner_process in self.task_runner_processes:
+            task_runner_process.join()
+        logger.info("Joined TaskRunner processes")
+
+    def __stop_metrics_provider_process(self):
+        self.__stop_process(self.metrics_provider_process)
+
+    def __stop_task_runner_processes(self):
+        for task_runner_process in self.task_runner_processes:
+            self.__stop_process(task_runner_process)
+
+    def __stop_process(self, process: Process):
+        if process is None:
+            return
+        try:
+            logger.debug(f"Terminating process: {process.pid}")
+            process.terminate()
+        except Exception as e:
+            logger.debug(f"Failed to terminate process: {process.pid}, reason: {e}")
+            process.kill()
+            logger.debug(f"Killed process: {process.pid}")
+
+
+# Setup centralized logging queue
+def _setup_logging_queue(configuration: Configuration):
+    queue = Queue()
+    if configuration:
+        configuration.apply_logging_config()
+        log_level = configuration.log_level
+        logger_format = configuration.logger_format
+    else:
+        log_level = logging.DEBUG
+        logger_format = None
+
+    logger.setLevel(log_level)
+
+    # start the logger process
+    logger_p = Process(target=__logger_process, args=(queue, log_level, logger_format))
+    logger_p.start()
+    return logger_p, queue
+
+
+# This process performs the centralized logging
+def __logger_process(queue, log_level, logger_format=None):
+    c_logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+    c_logger.setLevel(log_level)
+
+    # configure a stream handler
+    sh = logging.StreamHandler()
+    if logger_format:
+        formatter = logging.Formatter(logger_format)
+        sh.setFormatter(formatter)
+    c_logger.addHandler(sh)
+
+    # run forever
+    while True:
+        # consume a log message, block until one arrives
+        message = queue.get()
+        # check for shutdown
+        if message is None:
+            break
+        # log the message
+        c_logger.handle(message)
diff --git a/omagent_core/engine/automator/task_runner.py b/omagent_core/engine/automator/task_runner.py
new file mode 100644
index 0000000000000000000000000000000000000000..311a55aa5e25e451b2db30ad73eaba8414498067
--- /dev/null
+++ b/omagent_core/engine/automator/task_runner.py
@@ -0,0 +1,323 @@
+import os
+import sys
+import time
+import traceback
+
+from func_timeout import func_timeout, FunctionTimedOut
+
+from omagent_core.engine.orkes.orkes_workflow_client import workflow_client
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.configuration.aaas_config import AaasConfig
+from omagent_core.engine.configuration.settings.metrics_settings import \
+    MetricsSettings
+from omagent_core.engine.http.api.aaas_task_api import AaasTaskApi
+from omagent_core.engine.http.api.task_resource_api import TaskResourceApi
+from omagent_core.engine.http.api_client import ApiClient
+from omagent_core.engine.http.models.task import Task
+from omagent_core.engine.http.models.task_exec_log import TaskExecLog
+from omagent_core.engine.http.models.task_result import TaskResult
+from omagent_core.engine.http.rest import AuthorizationException
+from omagent_core.engine.telemetry.metrics_collector import MetricsCollector
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.engine.workflow.task.task_type import TaskType
+from omagent_core.utils.container import container
+from omagent_core.utils.handler import ConductorLogHandler
+from omagent_core.utils.logger import logging
+
+
+class TaskRunner:
+    def __init__(
+        self,
+        worker: BaseWorker,
+        configuration: Configuration = None,
+        aaas_config: AaasConfig = None,
+        metrics_settings: MetricsSettings = None,
+    ):
+        if not isinstance(worker, BaseWorker):
+            raise Exception(f"Invalid worker {type(worker)}")
+        self.worker = worker
+        self.__set_worker_properties()
+        if not isinstance(configuration, Configuration):
+            configuration = container.conductor_config
+        self.configuration = configuration
+        if not isinstance(aaas_config, AaasConfig):
+            aaas_config = container.aaas_config
+        self.aaas_config = aaas_config
+        self.metrics_collector = None
+        if metrics_settings is not None:
+            self.metrics_collector = MetricsCollector(metrics_settings)
+        self.task_client = TaskResourceApi(ApiClient(configuration=self.configuration))
+        self.aaas_task_client = AaasTaskApi(configuration=self.aaas_config)
+
+    def run(self) -> None:
+        task_names = ",".join(self.worker.task_definition_names)
+        logging.info(
+            f"Polling task {task_names} with domain {self.worker.get_domain()} with polling "
+            f"interval {self.worker.get_polling_interval_in_seconds()}"
+        )
+
+        while True:
+            try:
+                self.run_once()
+            except Exception as e:
+                pass
+
+    def run_once(self) -> None:
+        task = self.__poll_task()
+        if task is not None and task.task_id is not None:
+            task_result = self.__execute_task(task)
+            self.__update_task(task_result)
+        self.__wait_for_polling_interval()
+        self.worker.clear_task_definition_name_cache()
+
+    def __poll_task(self) -> Task:
+        task_definition_name = self.worker.get_task_definition_name()
+        if self.worker.paused():
+            logging.debug(f"Stop polling task for: {task_definition_name}")
+            return None
+        if self.metrics_collector is not None:
+            self.metrics_collector.increment_task_poll(task_definition_name)
+
+        try:
+            start_time = time.time()
+            domain = self.worker.get_domain()
+            params = {"workerid": self.worker.get_identity()}
+            # if domain is not None:
+            #     params["domain"] = domain
+            # task = self.task_client.poll(tasktype=task_definition_name, **params)
+            if self.worker.task_type and self.worker.task_type == TaskType.CUSTOM:
+                params["is_prod"] = self.aaas_config.is_prod
+                if not self.aaas_config.is_prod:
+                    params["domain"] = self.aaas_config.domain_token
+                tasks = self.aaas_task_client.batch_poll_from_aaas(task_definition_name, **params)
+                if len(tasks) > 0:
+                    task = tasks[0]
+                else:
+                    task = None
+            else:
+                if domain is not None:
+                    params["domain"] = domain
+                task = self.task_client.poll(tasktype=task_definition_name, **params)
+
+            finish_time = time.time()
+            time_spent = finish_time - start_time
+            if self.metrics_collector is not None:
+                self.metrics_collector.record_task_poll_time(
+                    task_definition_name, time_spent
+                )
+        except AuthorizationException as auth_exception:
+            if self.metrics_collector is not None:
+                self.metrics_collector.increment_task_poll_error(
+                    task_definition_name, type(auth_exception)
+                )
+            if auth_exception.invalid_token:
+                logging.fatal(
+                    f"failed to poll task {task_definition_name} due to invalid auth token"
+                )
+            else:
+                logging.fatal(
+                    f"failed to poll task {task_definition_name} error: {auth_exception.status} - {auth_exception.error_code}"
+                )
+            return None
+        except Exception as e:
+            if self.metrics_collector is not None:
+                self.metrics_collector.increment_task_poll_error(
+                    task_definition_name, type(e)
+                )
+            logging.error(
+                f"Failed to poll task for: {task_definition_name}, reason: {traceback.format_exc()}"
+            )
+            return None
+        # if task is not None:
+        #     logging.debug(
+        #         f'Polled task: {task_definition_name}, worker_id: {self.worker.get_identity()}, domain: {self.worker.get_domain()}')
+        return task
+
+    def __execute_task(self, task: Task) -> TaskResult:
+        if not isinstance(task, Task):
+            return None
+        task_definition_name = self.worker.get_task_definition_name()
+        logging.info(
+            "Executing task, id: {task_id}, workflow_instance_id: {workflow_instance_id}, task_definition_name: {task_definition_name}".format(
+                task_id=task.task_id,
+                workflow_instance_id=task.workflow_instance_id,
+                task_definition_name=task_definition_name,
+            )
+        )
+        try:
+            _input = workflow_client.get_workflow(task.workflow_instance_id).input
+            if 'conversationInfo' in _input:
+                task.conversation_info = _input.get('conversationInfo', {})
+                logging.info(f'conversation_info: {task.conversation_info}')
+            
+            conductor_log_handler = ConductorLogHandler(self.task_client)
+            conductor_log_handler.set_task_id(task.task_id)
+            logging.addHandler(conductor_log_handler)
+            start_time = time.time()
+            if task.response_timeout_seconds:
+                task_result = func_timeout(
+                    timeout=task.response_timeout_seconds,
+                    func=self.worker.execute,
+                    args=(task,)
+                )
+            else:
+                task_result = self.worker.execute(task)
+            finish_time = time.time()
+            time_spent = finish_time - start_time
+            if self.metrics_collector is not None:
+                self.metrics_collector.record_task_execute_time(
+                    task_definition_name, time_spent
+                )
+                self.metrics_collector.record_task_result_payload_size(
+                    task_definition_name, sys.getsizeof(task_result)
+                )
+            logging.removeHandler(conductor_log_handler)
+            logging.debug(
+                "Executed task, id: {task_id}, workflow_instance_id: {workflow_instance_id}, task_definition_name: {task_definition_name}".format(
+                    task_id=task.task_id,
+                    workflow_instance_id=task.workflow_instance_id,
+                    task_definition_name=task_definition_name,
+                )
+            )
+        except FunctionTimedOut:
+            task_result = TaskResult(
+                task_id=task.task_id,
+                workflow_instance_id=task.workflow_instance_id,
+                worker_id=self.worker.get_identity(),
+            )
+            task_result.status = "FAILED"
+            task_result.reason_for_incompletion = 'Task running timeout'
+            task_result.logs = [
+                TaskExecLog(
+                    traceback.format_exc(), task_result.task_id, int(time.time())
+                )
+            ]
+        except Exception as e:
+            if self.metrics_collector is not None:
+                self.metrics_collector.increment_task_execution_error(
+                    task_definition_name, type(e)
+                )
+            task_result = TaskResult(
+                task_id=task.task_id,
+                workflow_instance_id=task.workflow_instance_id,
+                worker_id=self.worker.get_identity(),
+            )
+            task_result.status = "FAILED"
+            task_result.reason_for_incompletion = str(e)
+            task_result.logs = [
+                TaskExecLog(
+                    traceback.format_exc(), task_result.task_id, int(time.time())
+                )
+            ]
+            logging.error(
+                "Failed to execute task, id: {task_id}, workflow_instance_id: {workflow_instance_id}, task_definition_name: {task_definition_name}, reason: {reason}".format(
+                    task_id=task.task_id,
+                    workflow_instance_id=task.workflow_instance_id,
+                    task_definition_name=task_definition_name,
+                    reason=traceback.format_exc(),
+                )
+            )
+        return task_result
+
+    def __update_task(self, task_result: TaskResult):
+        if not isinstance(task_result, TaskResult):
+            return None
+        task_definition_name = self.worker.get_task_definition_name()
+        logging.debug(
+            "Updating task, id: {task_id}, workflow_instance_id: {workflow_instance_id}, task_definition_name: {task_definition_name}".format(
+                task_id=task_result.task_id,
+                workflow_instance_id=task_result.workflow_instance_id,
+                task_definition_name=task_definition_name,
+            )
+        )
+        for attempt in range(4):
+            if attempt > 0:
+                # Wait for [10s, 20s, 30s] before next attempt
+                time.sleep(attempt * 10)
+            try:
+                # response = self.task_client.update_task(body=task_result)
+                if self.worker.task_type and self.worker.task_type == TaskType.CUSTOM:
+                    response = self.aaas_task_client.update_task_to_aaas(body=task_result)
+                else:
+                    response = self.task_client.update_task(body=task_result)
+                logging.debug(
+                    "Updated task, id: {task_id}, workflow_instance_id: {workflow_instance_id}, task_definition_name: {task_definition_name}, response: {response}".format(
+                        task_id=task_result.task_id,
+                        workflow_instance_id=task_result.workflow_instance_id,
+                        task_definition_name=task_definition_name,
+                        response=response,
+                    )
+                )
+                return response
+            except Exception as e:
+                if self.metrics_collector is not None:
+                    self.metrics_collector.increment_task_update_error(
+                        task_definition_name, type(e)
+                    )
+                logging.error(
+                    "Failed to update task, id: {task_id}, workflow_instance_id: {workflow_instance_id}, "
+                    "task_definition_name: {task_definition_name}, reason: {reason}".format(
+                        task_id=task_result.task_id,
+                        workflow_instance_id=task_result.workflow_instance_id,
+                        task_definition_name=task_definition_name,
+                        reason=traceback.format_exc(),
+                    )
+                )
+        return None
+
+    def __wait_for_polling_interval(self) -> None:
+        polling_interval = self.worker.get_polling_interval_in_seconds()
+        time.sleep(polling_interval)
+
+    def __set_worker_properties(self) -> None:
+        # If multiple tasks are supplied to the same worker, then only first
+        # task will be considered for setting worker properties
+        task_type = self.worker.get_task_definition_name()
+
+        domain = self.__get_property_value_from_env("domain", task_type)
+        if domain:
+            self.worker.domain = domain
+        else:
+            self.worker.domain = self.worker.get_domain()
+
+        polling_interval = self.__get_property_value_from_env(
+            "polling_interval", task_type
+        )
+        if polling_interval:
+            try:
+                self.worker.poll_interval = float(polling_interval)
+            except Exception as e:
+                logging.error(
+                    f"error reading and parsing the polling interval value {polling_interval}"
+                )
+                self.worker.poll_interval = (
+                    self.worker.get_polling_interval_in_seconds()
+                )
+
+        if polling_interval:
+            try:
+                self.worker.poll_interval = float(polling_interval)
+                polling_interval_initialized = True
+            except Exception as e:
+                logging.error(
+                    "Exception in reading polling interval from environment variable: {0}.".format(
+                        str(e)
+                    )
+                )
+
+    def __get_property_value_from_env(self, prop, task_type):
+        """
+        get the property from the env variable
+        e.g. conductor_worker_"prop" or conductor_worker_"task_type"_"prop"
+        """
+        prefix = "conductor_worker"
+        # Look for generic property in both case environment variables
+        key = prefix + "_" + prop
+        value_all = os.getenv(key, os.getenv(key.upper()))
+
+        # Look for task specific property in both case environment variables
+        key_small = prefix + "_" + task_type + "_" + prop
+        key_upper = prefix.upper() + "_" + task_type + "_" + prop.upper()
+        value = os.getenv(key_small, os.getenv(key_upper, value_all))
+        return value
diff --git a/omagent_core/engine/automator/utils.py b/omagent_core/engine/automator/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..f272984f35936bcf397ba05076c3a83bfdfcf74c
--- /dev/null
+++ b/omagent_core/engine/automator/utils.py
@@ -0,0 +1,135 @@
+import dataclasses
+import datetime
+import inspect
+import logging
+import typing
+from typing import List
+
+from dacite import from_dict
+from omagent_core.engine.configuration.configuration import Configuration
+from requests.structures import CaseInsensitiveDict
+
+logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+simple_types = {int, float, str, bool, datetime.date, datetime.datetime, object}
+dict_types = {dict, typing.Dict, CaseInsensitiveDict}
+collection_types = {list, List, typing.Set}
+
+
+def convert_from_dict_or_list(cls: type, data: typing.Union[dict, list]) -> object:
+    is_list = type(data) in collection_types
+    if is_list:
+        val_list = []
+        for val in data:
+            generic_types = typing.get_args(cls)[0]
+            converted = convert_from_dict(generic_types, val)
+            val_list.append(converted)
+        return val_list
+    return convert_from_dict(cls, data)
+
+
+def convert_from_dict(cls: type, data: dict) -> object:
+    if data is None:
+        return data
+
+    if type(data) == cls:
+        return data
+
+    if dataclasses.is_dataclass(cls):
+        return from_dict(data_class=cls, data=data)
+
+    typ = type(data)
+    if not (
+        (
+            str(typ).startswith("dict[")
+            or str(typ).startswith("typing.Dict[")
+            or str(typ).startswith("requests.structures.CaseInsensitiveDict[")
+            or typ == dict
+            or str(typ).startswith("OrderedDict[")
+        )
+    ):
+        data = {}
+
+    members = inspect.signature(cls.__init__).parameters
+    kwargs = {}
+
+    for member in members:
+        if "self" == member:
+            continue
+        typ = members[member].annotation
+        generic_types = typing.get_args(members[member].annotation)
+
+        if typ in simple_types:
+            if member in data:
+                kwargs[member] = data[member]
+            else:
+                kwargs[member] = members[member].default
+        elif (
+            str(typ).startswith("typing.List[")
+            or str(typ).startswith("typing.Set[")
+            or str(typ).startswith("list[")
+        ):
+            values = []
+            generic_type = object
+            if len(generic_types) > 0:
+                generic_type = generic_types[0]
+            for val in data[member]:
+                values.append(get_value(generic_type, val))
+            kwargs[member] = values
+        elif (
+            str(typ).startswith("dict[")
+            or str(typ).startswith("typing.Dict[")
+            or str(typ).startswith("requests.structures.CaseInsensitiveDict[")
+            or typ == dict
+            or str(typ).startswith("OrderedDict[")
+        ):
+
+            values = {}
+            generic_type = object
+            if len(generic_types) > 1:
+                generic_type = generic_types[1]
+            for k in data[member]:
+                v = data[member][k]
+                values[k] = get_value(generic_type, v)
+            kwargs[member] = values
+        elif typ == inspect.Parameter.empty:
+            if inspect.Parameter.VAR_KEYWORD == members[member].kind:
+                if type(data) in dict_types:
+                    kwargs.update(data)
+                else:
+                    kwargs.update(data[member])
+            else:
+                # kwargs[member] = data[member]
+                kwargs.update(data)
+        else:
+            kwargs[member] = convert_from_dict(typ, data[member])
+
+    return cls(**kwargs)
+
+
+def get_value(typ: type, val: object) -> object:
+    if typ in simple_types:
+        return val
+    elif (
+        str(typ).startswith("typing.List[")
+        or str(typ).startswith("typing.Set[")
+        or str(typ).startswith("list[")
+    ):
+        values = []
+        for val in val:
+            converted = get_value(type(val), val)
+            values.append(converted)
+        return values
+    elif (
+        str(typ).startswith("dict[")
+        or str(typ).startswith("typing.Dict[")
+        or str(typ).startswith("requests.structures.CaseInsensitiveDict[")
+        or typ == dict
+    ):
+        values = {}
+        for k in val:
+            v = val[k]
+            values[k] = get_value(object, v)
+        return values
+    else:
+        return convert_from_dict(typ, val)
diff --git a/omagent_core/engine/configuration/__init__.py b/omagent_core/engine/configuration/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/configuration/aaas_config.py b/omagent_core/engine/configuration/aaas_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..221e1e3a012ce3286e33c2591e786367b37f22f0
--- /dev/null
+++ b/omagent_core/engine/configuration/aaas_config.py
@@ -0,0 +1,68 @@
+import logging
+import os
+import time
+from typing import Any, Optional
+
+from omagent_core.engine.configuration.settings.authentication_settings import \
+    AuthenticationSettings
+from pydantic import Field
+from pydantic_settings import BaseSettings
+
+AAAS_TEMPLATE_CONFIG = {
+    "name": "AaasConfig",
+    "base_url": {
+        "value": "http://localhost:30002",
+        "description": "The aaas task server API endpoint",
+        "env_var": "AAAS_TASK_SERVER_URL",
+    },
+    "token": {
+        "value": None,
+        "description": "The authorization token",
+        "env_var": "AAAS_TOKEN",
+    },
+    "enable": {
+        "value": True,
+        "description": "Whether to enable the aaas task server",
+        "env_var": "AAAS_ENABLE",
+    },
+    "domain_token": {
+        "value": None,
+        "description": "The domain token",
+        "env_var": "DOMAIN_TOKEN",
+    },
+    "is_prod": {
+        "value": False,
+        "description": "Whether it is a production environment",
+        "env_var": "IS_PROD",
+    }
+}
+
+
+class AaasConfig(BaseSettings):
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+
+    base_url: str = Field(
+        default="http://localhost:30002", description="The aaas task server API endpoint"
+    )
+    token: Optional[str] = Field(
+        default=None,
+        description="The authorization token",
+    )
+    enable: bool = Field(
+        default=True,
+        description="Whether to enable the aaas task server",
+    )
+    domain_token: Optional[str] = Field(
+        default=None,
+        description="The domain token",
+    )
+    is_prod: bool = Field(
+        default=False,
+        description="Whether it is a production environment",
+    )
+
+    def model_post_init(self, __context: Any) -> None:
+        self.host = self.base_url + "/api"
diff --git a/omagent_core/engine/configuration/configuration.py b/omagent_core/engine/configuration/configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..f103642b40bb91a4f4cb70e50dbf878ec1acffcd
--- /dev/null
+++ b/omagent_core/engine/configuration/configuration.py
@@ -0,0 +1,154 @@
+import logging
+import os
+import time
+from typing import Any, Optional
+
+from omagent_core.engine.configuration.settings.authentication_settings import \
+    AuthenticationSettings
+from pydantic import Field
+from pydantic_settings import BaseSettings
+
+TEMPLATE_CONFIG = {
+    "name": "Configuration",
+    "base_url": {
+        "value": "http://localhost:8080",
+        "description": "The Conductor Server API endpoint",
+        "env_var": "CONDUCTOR_SERVER_URL",
+    },
+    "auth_key": {
+        "value": None,
+        "description": "The authorization key",
+        "env_var": "AUTH_KEY",
+    },
+    "auth_secret": {
+        "value": None,
+        "description": "The authorization secret",
+        "env_var": "CONDUCTOR_AUTH_SECRET",
+    },
+    "auth_token_ttl_min": {
+        "value": 45,
+        "description": "The authorization token refresh interval in minutes.",
+        "env_var": "AUTH_TOKEN_TTL_MIN",
+    },
+    "debug": {"value": False, "description": "Debug mode", "env_var": "DEBUG"},
+}
+
+
+class Configuration(BaseSettings):
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+
+    base_url: str = Field(
+        default="http://localhost:8080", description="The Conductor Server API endpoint"
+    )
+    auth_key: Optional[str] = Field(default=None, description="The authorization key")
+    auth_secret: Optional[str] = Field(
+        default=None,
+        description="The authorization secret",
+    )
+    auth_token_ttl_min: int = Field(
+        default=45, description="The authorization token refresh interval in minutes."
+    )
+    debug: bool = Field(default=False, description="Debug mode")
+
+    def model_post_init(self, __context: Any) -> None:
+        self.__log_level = logging.DEBUG if self.debug else logging.INFO
+        self.AUTH_TOKEN = None
+        self.temp_folder_path = None
+        self.host = self.base_url + "/api"
+        self.__ui_host = self.host.replace("8080/api", "5000")
+        if self.auth_key and self.auth_secret:
+            self.authentication_settings = AuthenticationSettings(
+                key_id=self.auth_key, key_secret=self.auth_secret
+            )
+        else:
+            self.authentication_settings = None
+
+        # Log format
+        self.logger_format = "%(asctime)s %(name)-12s %(levelname)-8s %(message)s"
+
+        # SSL/TLS verification
+        # Set this to false to skip verifying SSL certificate when calling API
+        # from https server.
+        self.verify_ssl = True
+        # Set this to customize the certificate file to verify the peer.
+        self.ssl_ca_cert = None
+        # client certificate file
+        self.cert_file = None
+        # client key file
+        self.key_file = None
+        # Set this to True/False to enable/disable SSL hostname verification.
+        self.assert_hostname = None
+
+        # Proxy URL
+        self.proxy = None
+        # Safe chars for path_param
+        self.safe_chars_for_path_param = ""
+
+        # Provide an alterative to requests.Session() for HTTP connection.
+        self.http_connection = None
+
+        # not updated yet
+        self.token_update_time = 0
+        self.auth_token_ttl_msec = self.auth_token_ttl_min * 60 * 1000
+
+    @property
+    def logger_format(self):
+        """The logger format.
+
+        The logger_formatter will be updated when sets logger_format.
+
+        :param value: The format string.
+        :type: str
+        """
+        return self.__logger_format
+
+    @logger_format.setter
+    def logger_format(self, value):
+        """The logger format.
+
+        The logger_formatter will be updated when sets logger_format.
+
+        :param value: The format string.
+        :type: str
+        """
+        self.__logger_format = value
+
+    @property
+    def log_level(self):
+        """The log level.
+
+        The log_level will be updated when sets logger_format.
+
+        :param value: The format string.
+        :type: str
+        """
+        return self.__log_level
+
+    @property
+    def ui_host(self):
+        """
+
+        The log_level will be updated when sets logger_format.
+
+        :param value: The format string.
+        :type: str
+        """
+        return self.__ui_host
+
+    def apply_logging_config(self, log_format: str = None, level=None):
+        if log_format is None:
+            log_format = self.logger_format
+        if level is None:
+            level = self.__log_level
+        logging.basicConfig(format=log_format, level=level)
+
+    @staticmethod
+    def get_logging_formatted_name(name):
+        return f"[{os.getpid()}] {name}"
+
+    def update_token(self, token: str) -> None:
+        self.AUTH_TOKEN = token
+        self.token_update_time = round(time.time() * 1000)
diff --git a/omagent_core/engine/configuration/settings/__init__.py b/omagent_core/engine/configuration/settings/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/configuration/settings/authentication_settings.py b/omagent_core/engine/configuration/settings/authentication_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..ca50c547f90d1869bd8394838e3fbacba5b41120
--- /dev/null
+++ b/omagent_core/engine/configuration/settings/authentication_settings.py
@@ -0,0 +1,4 @@
+class AuthenticationSettings:
+    def __init__(self, key_id: str, key_secret: str):
+        self.key_id = key_id
+        self.key_secret = key_secret
diff --git a/omagent_core/engine/configuration/settings/metrics_settings.py b/omagent_core/engine/configuration/settings/metrics_settings.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1382f3775433249147b6d7dab55ec9188caf360
--- /dev/null
+++ b/omagent_core/engine/configuration/settings/metrics_settings.py
@@ -0,0 +1,33 @@
+import logging
+import os
+from pathlib import Path
+
+from omagent_core.engine.configuration.configuration import Configuration
+
+logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+
+def get_default_temporary_folder() -> str:
+    return f"{str(Path.home())}/tmp/"
+
+
+class MetricsSettings:
+    def __init__(
+        self,
+        directory: str = None,
+        file_name: str = "metrics.log",
+        update_interval: float = 0.1,
+    ):
+        if directory is None:
+            directory = get_default_temporary_folder()
+        self.__set_dir(directory)
+        self.file_name = file_name
+        self.update_interval = update_interval
+
+    def __set_dir(self, dir: str) -> None:
+        if not os.path.isdir(dir):
+            try:
+                os.mkdir(dir)
+            except Exception as e:
+                logger.warning("Failed to create metrics temporary folder, reason: ", e)
+        self.directory = dir
diff --git a/omagent_core/engine/event/__init__.py b/omagent_core/engine/event/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/event/event_client.py b/omagent_core/engine/event/event_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d16174dd2b25b28f493d0348d92945c9c193f2d
--- /dev/null
+++ b/omagent_core/engine/event/event_client.py
@@ -0,0 +1,33 @@
+from omagent_core.engine.event.queue.queue_configuration import \
+    QueueConfiguration
+from omagent_core.engine.http.api.event_resource_api import EventResourceApi
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class EventClient:
+    def __init__(self, api_client: ApiClient):
+        self.client = EventResourceApi(api_client)
+
+    def delete_queue_configuration(
+        self, queue_configuration: QueueConfiguration
+    ) -> None:
+        return self.client.delete_queue_config(
+            queue_name=queue_configuration.queue_name,
+            queue_type=queue_configuration.queue_type,
+        )
+
+    def get_kafka_queue_configuration(self, queue_topic: str) -> QueueConfiguration:
+        return self.get_queue_configuration(
+            queue_type="kafka",
+            queue_name=queue_topic,
+        )
+
+    def get_queue_configuration(self, queue_type: str, queue_name: str):
+        return self.client.get_queue_config(queue_type, queue_name)
+
+    def put_queue_configuration(self, queue_configuration: QueueConfiguration):
+        return self.client.put_queue_config(
+            body=queue_configuration.get_worker_configuration(),
+            queue_name=queue_configuration.queue_name,
+            queue_type=queue_configuration.queue_type,
+        )
diff --git a/omagent_core/engine/event/queue/__init__.py b/omagent_core/engine/event/queue/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/event/queue/kafka_queue_configuration.py b/omagent_core/engine/event/queue/kafka_queue_configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..90f8c79e3d0407a7d03544a4b53e4aeb82f44bd4
--- /dev/null
+++ b/omagent_core/engine/event/queue/kafka_queue_configuration.py
@@ -0,0 +1,36 @@
+from typing import Any, Dict
+
+from omagent_core.engine.event.queue.queue_configuration import \
+    QueueConfiguration
+from omagent_core.engine.event.queue.queue_worker_configuration import \
+    QueueWorkerConfiguration
+
+
+class KafkaQueueConfiguration(QueueConfiguration):
+    def __init__(self, queue_topic_name: str):
+        super().__init__(queue_topic_name, "kafka")
+
+    def get_worker_configuration(self) -> Dict[str, Any]:
+        worker_configuration = {}
+        for required_key in ["consumer", "producer"]:
+            if required_key not in self.worker_configuration:
+                raise RuntimeError(f"required key not present: {required_key}")
+        for key, value in self.worker_configuration.items():
+            worker_configuration[key] = value.configuration
+        return worker_configuration
+
+
+class KafkaConsumerConfiguration(QueueWorkerConfiguration):
+    def __init__(self, bootstrap_servers_config: str):
+        super().__init__()
+        super().add_configuration(
+            key="bootstrap.servers", value=bootstrap_servers_config
+        )
+
+
+class KafkaProducerConfiguration(QueueWorkerConfiguration):
+    def __init__(self, bootstrap_servers_config: str):
+        super().__init__()
+        super().add_configuration(
+            key="bootstrap.servers", value=bootstrap_servers_config
+        )
diff --git a/omagent_core/engine/event/queue/queue_configuration.py b/omagent_core/engine/event/queue/queue_configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..1711cf7bd79cb8b1b5354a5df8fcbf07c555714e
--- /dev/null
+++ b/omagent_core/engine/event/queue/queue_configuration.py
@@ -0,0 +1,24 @@
+from abc import ABC
+from typing import Any, Dict
+
+from omagent_core.engine.event.queue.queue_worker_configuration import \
+    QueueWorkerConfiguration
+
+
+class QueueConfiguration(ABC):
+    WORKER_CONSUMER_KEY = "consumer"
+    WORKER_PRODUCER_KEY = "producer"
+
+    def __init__(self, queue_name: str, queue_type: str):
+        self.queue_name = queue_name
+        self.queue_type = queue_type
+        self.worker_configuration = {}
+
+    def add_consumer(self, worker_configuration: QueueWorkerConfiguration) -> None:
+        self.worker_configuration[self.WORKER_CONSUMER_KEY] = worker_configuration
+
+    def add_producer(self, worker_configuration: QueueWorkerConfiguration) -> None:
+        self.worker_configuration[self.WORKER_PRODUCER_KEY] = worker_configuration
+
+    def get_worker_configuration(self) -> Dict[str, Any]:
+        raise NotImplementedError
diff --git a/omagent_core/engine/event/queue/queue_worker_configuration.py b/omagent_core/engine/event/queue/queue_worker_configuration.py
new file mode 100644
index 0000000000000000000000000000000000000000..449b6757cdd3857b2af1e3518459f5f085adcd84
--- /dev/null
+++ b/omagent_core/engine/event/queue/queue_worker_configuration.py
@@ -0,0 +1,6 @@
+class QueueWorkerConfiguration:
+    def __init__(self):
+        self.configuration = {}
+
+    def add_configuration(self, key: str, value: str) -> None:
+        self.configuration[key] = value
diff --git a/omagent_core/engine/exceptions/__init__.py b/omagent_core/engine/exceptions/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/exceptions/api_error.py b/omagent_core/engine/exceptions/api_error.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4dbed09ff0eae2fa7135872cc6c83bc29dcaa10
--- /dev/null
+++ b/omagent_core/engine/exceptions/api_error.py
@@ -0,0 +1,19 @@
+from enum import Enum
+
+
+class APIErrorCode(str, Enum):
+    NOT_FOUND = (404,)
+    FORBIDDEN = 403
+    CONFLICT = 409
+    BAD_REQUEST = 400
+    REQUEST_TIMEOUT = 408
+    UNKNOWN = 0
+
+
+class APIError(Exception):
+
+    def __init__(self, status=None, reason=None, http_resp=None, body=None):
+        super().__init__(status, reason, http_resp, body)
+
+    def __str__(self):
+        return "APIError: code={} message={}".format(self.code, self.message)
diff --git a/omagent_core/engine/exceptions/api_exception_handler.py b/omagent_core/engine/exceptions/api_exception_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c2ccf8d8936a56aa4e283824effecfd920e1310
--- /dev/null
+++ b/omagent_core/engine/exceptions/api_exception_handler.py
@@ -0,0 +1,56 @@
+import json
+
+from omagent_core.engine.exceptions.api_error import APIError, APIErrorCode
+from omagent_core.engine.http.rest import ApiException
+
+STATUS_TO_MESSAGE_DEFAULT_MAPPING = {
+    400: "Invalid request",
+    403: "Access forbidden",
+    404: "Resource not found",
+    408: "Request timed out",
+    409: "Resource exists already",
+}
+
+
+def api_exception_handler(function):
+    def inner_function(*args, **kwargs):
+        try:
+            return function(*args, **kwargs)
+        except ApiException as e:
+
+            if e.status == 404:
+                code = APIErrorCode.NOT_FOUND
+            elif e.status == 403:
+                code = APIErrorCode.FORBIDDEN
+            elif e.status == 409:
+                code = APIErrorCode.CONFLICT
+            elif e.status == 400:
+                code = APIErrorCode.BAD_REQUEST
+            elif e.status == 408:
+                code = APIErrorCode.REQUEST_TIMEOUT
+            else:
+                code = APIErrorCode.UNKNOWN
+
+            message = STATUS_TO_MESSAGE_DEFAULT_MAPPING[e.status]
+
+            try:
+                if e.body:
+                    error = json.loads(e.body)
+                    message = error["message"]
+            except ValueError:
+                message = e.body
+
+            finally:
+                raise APIError(code, message)
+
+    return inner_function
+
+
+def for_all_methods(decorator, exclude=[]):
+    def decorate(cls):
+        for attr in cls.__dict__:
+            if callable(getattr(cls, attr)) and attr not in exclude:
+                setattr(cls, attr, decorator(getattr(cls, attr)))
+        return cls
+
+    return decorate
diff --git a/omagent_core/engine/helpers/__init__.py b/omagent_core/engine/helpers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/helpers/helper.py b/omagent_core/engine/helpers/helper.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6b34f39c7e0ac3c70ff9da8d4372e9bac06cf73
--- /dev/null
+++ b/omagent_core/engine/helpers/helper.py
@@ -0,0 +1,196 @@
+import datetime
+import logging
+import re
+
+import omagent_core.engine.http.models as http_models
+import six
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http import rest
+from requests.structures import CaseInsensitiveDict
+
+logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+
+class ObjectMapper(object):
+    PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types
+    NATIVE_TYPES_MAPPING = {
+        "int": int,
+        "long": int if six.PY3 else long,  # noqa: F821
+        "float": float,
+        "str": str,
+        "bool": bool,
+        "date": datetime.date,
+        "datetime": datetime.datetime,
+        "object": object,
+    }
+
+    def to_json(self, obj):
+
+        if obj is None:
+            return None
+        elif isinstance(obj, self.PRIMITIVE_TYPES):
+            return obj
+        elif isinstance(obj, list):
+            return [self.to_json(sub_obj) for sub_obj in obj]
+        elif isinstance(obj, tuple):
+            return tuple(self.to_json(sub_obj) for sub_obj in obj)
+        elif isinstance(obj, (datetime.datetime, datetime.date)):
+            return obj.isoformat()
+
+        if isinstance(obj, dict) or isinstance(obj, CaseInsensitiveDict):
+            obj_dict = obj
+        else:
+            # Convert model obj to dict except
+            # attributes `swagger_types`, `attribute_map`
+            # and attributes which value is not None.
+            # Convert attribute name to json key in
+            # model definition for request.
+            if hasattr(obj, "attribute_map") and hasattr(obj, "swagger_types"):
+                obj_dict = {
+                    obj.attribute_map[attr]: getattr(obj, attr)
+                    for attr, _ in six.iteritems(obj.swagger_types)
+                    if getattr(obj, attr) is not None
+                }
+            else:
+                obj_dict = {
+                    name: getattr(obj, name)
+                    for name in vars(obj)
+                    if getattr(obj, name) is not None
+                }
+
+        return {key: self.to_json(val) for key, val in six.iteritems(obj_dict)}
+
+    def from_json(self, data, klass):
+        return self.__deserialize(data, klass)
+
+    def __deserialize(self, data, klass):
+        if data is None:
+            return None
+
+        if type(klass) == str:
+            if klass.startswith("list["):
+                sub_kls = re.match(r"list\[(.*)\]", klass).group(1)
+                return [self.__deserialize(sub_data, sub_kls) for sub_data in data]
+
+            if klass.startswith("dict("):
+                sub_kls = re.match(r"dict\(([^,]*), (.*)\)", klass).group(2)
+                return {
+                    k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data)
+                }
+
+            # convert str to class
+            if klass in self.NATIVE_TYPES_MAPPING:
+                klass = self.NATIVE_TYPES_MAPPING[klass]
+            else:
+                klass = getattr(http_models, klass)
+
+        if klass in self.PRIMITIVE_TYPES:
+            return self.__deserialize_primitive(data, klass)
+        elif klass == object:
+            return self.__deserialize_object(data)
+        elif klass == datetime.date:
+            return self.__deserialize_date(data)
+        elif klass == datetime.datetime:
+            return self.__deserialize_datatime(data)
+        else:
+            return self.__deserialize_model(data, klass)
+
+    def __deserialize_primitive(self, data, klass):
+        """Deserializes string to primitive type.
+
+        :param data: str.
+        :param klass: class literal.
+
+        :return: int, long, float, str, bool.
+        """
+        try:
+            if klass == str and type(data) == bytes:
+                return self.__deserialize_bytes_to_str(data)
+            return klass(data)
+        except UnicodeEncodeError:
+            return six.text_type(data)
+        except TypeError:
+            return data
+
+    def __deserialize_bytes_to_str(self, data):
+        return data.decode("utf-8")
+
+    def __deserialize_object(self, value):
+        """Return a original value.
+
+        :return: object.
+        """
+        return value
+
+    def __deserialize_date(self, string):
+        """Deserializes string to date.
+
+        :param string: str.
+        :return: date.
+        """
+        try:
+            from dateutil.parser import parse
+
+            return parse(string).date()
+        except ImportError:
+            return string
+        except ValueError:
+            raise rest.ApiException(
+                status=0, reason="Failed to parse `{0}` as date object".format(string)
+            )
+
+    def __deserialize_datatime(self, string):
+        """Deserializes string to datetime.
+
+        The string should be in iso8601 datetime format.
+
+        :param string: str.
+        :return: datetime.
+        """
+        try:
+            from dateutil.parser import parse
+
+            return parse(string)
+        except ImportError:
+            return string
+        except ValueError:
+            raise rest.ApiException(
+                status=0,
+                reason=("Failed to parse `{0}` as datetime object".format(string)),
+            )
+
+    def __hasattr(self, object, name):
+        return name in object.__class__.__dict__
+
+    def __deserialize_model(self, data, klass):
+        if not klass.swagger_types and not self.__hasattr(
+            klass, "get_real_child_model"
+        ):
+            return data
+
+        kwargs = {}
+        if klass.swagger_types is not None:
+            for attr, attr_type in six.iteritems(klass.swagger_types):
+                if (
+                    data is not None
+                    and klass.attribute_map[attr] in data
+                    and isinstance(data, (list, dict))
+                ):
+                    value = data[klass.attribute_map[attr]]
+                    kwargs[attr] = self.__deserialize(value, attr_type)
+
+        instance = klass(**kwargs)
+
+        if (
+            isinstance(instance, dict)
+            and klass.swagger_types is not None
+            and isinstance(data, dict)
+        ):
+            for key, value in data.items():
+                if key not in klass.swagger_types:
+                    instance[key] = value
+        if self.__hasattr(instance, "get_real_child_model"):
+            klass_name = instance.get_real_child_model(data)
+            if klass_name:
+                instance = self.__deserialize(data, klass_name)
+        return instance
diff --git a/omagent_core/engine/http/__init__.py b/omagent_core/engine/http/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/http/api/__init__.py b/omagent_core/engine/http/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/http/api/aaas_task_api.py b/omagent_core/engine/http/api/aaas_task_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..e55f8ad734f011bc4ddb0846b2ab577773919a10
--- /dev/null
+++ b/omagent_core/engine/http/api/aaas_task_api.py
@@ -0,0 +1,158 @@
+import requests
+import json
+from omagent_core.engine.http.models.task import Task
+import logging
+
+class AaasTaskApi(object):
+
+    def __init__(self, configuration=None):
+        self.configuration = configuration
+    
+    def batch_poll_from_aaas(self, tasktype, **kwargs):
+        """Batch retrieve tasks from AaaS
+
+        Args:
+            tasktype (str): Required. The type of task to poll
+            workerid (str): Optional. Worker node ID
+            domain (str): Optional. Domain name
+            count (int): Optional. Batch size
+            
+        Returns:
+            list[Task]: List of Task objects
+            If async_req=True, returns the request thread
+        """
+        kwargs['_return_http_data_only'] = True
+        if kwargs.get('async_req'):
+            return self.batch_poll_from_aaas_with_http_info(tasktype, **kwargs)
+        else:
+            (data) = self.batch_poll_from_aaas_with_http_info(tasktype, **kwargs)
+            return data
+
+    def batch_poll_from_aaas_with_http_info(self, tasktype, **kwargs):
+        """Implementation of batch task retrieval from AaaS
+
+        Args:
+            tasktype (str): Required. The type of task to poll
+            workerid (str): Optional. Worker node ID
+            domain (str): Optional. Domain name
+            count (int): Optional. Batch size
+            
+        Returns:
+            list[Task]: List of Task objects
+            If async_req=True, returns the request thread
+        """
+        url = self.configuration.host + "/v1/batchPoll"
+        token = self.configuration.token
+        
+        headers = {
+            "Authorization": f"Bearer {token}",
+            "Content-Type": "application/json"
+        }
+
+        data = {
+            "batchSize": kwargs.get('count', 1),
+            "domain": kwargs.get('domain', None),
+            "isProd": kwargs.get('is_prod', False),
+            "taskDefName": tasktype,
+            "workerId": kwargs.get('workerid', 'default_worker')
+        }
+
+        try:
+            response = requests.post(url, headers=headers, json=data)
+            response.raise_for_status()
+            data_json = response.json()
+            
+            # Convert data structure to list of Task objects
+            tasks = []
+            if data_json.get('code') == 200 and data_json.get('data'):
+                for task_data in data_json['data']:
+                    # Create Task instance
+                    task = Task()
+                    
+                    # Set basic properties
+                    task.input_data = json.loads(task_data['payload']) if task_data.get('payload') else {}
+                    task.reference_task_name = tasktype
+                    task.status = 'IN_PROGRESS'
+                    
+                    # Parse bizMeta
+                    if task_data.get('bizMeta'):
+                        biz_meta = json.loads(task_data['bizMeta'])
+                        task.workflow_instance_id = biz_meta.get('workflowInstanceId')
+                        task.task_id = biz_meta.get('taskId')
+                        task.biz_meta = task_data['bizMeta']
+                    
+                    # Add callback URL
+                    if task_data.get('callbackUrl'):
+                        task.callback_url = task_data['callbackUrl']
+                        
+                    tasks.append(task)
+                    
+            return tasks
+            
+        except requests.exceptions.RequestException as e:
+            logging.error(f"Failed to fetch tasks from AaaS: {str(e)}")
+            raise
+
+    def update_task_to_aaas(self, body, **kwargs):
+        """Update task status to AaaS
+
+        Example:
+            >>> thread = api.update_task(body, async_req=True)
+            >>> result = thread.get()
+
+        Args:
+            body (TaskResult): Required. The task result to update
+            async_req (bool): Optional. If True, makes an asynchronous HTTP request
+            
+        Returns:
+            str: Success indicator
+            If async_req=True, returns the request thread
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_task_to_aaas_with_http_info(body, **kwargs)
+        else:
+            (data) = self.update_task_to_aaas_with_http_info(body, **kwargs)
+            return data
+
+
+    def update_task_to_aaas_with_http_info(self, body, **kwargs):
+        """Implementation of task status update to AaaS
+
+        Args:
+            body (TaskResult): Required. The task result to update
+            
+        Returns:
+            bool: True if update successful, False otherwise
+            If async_req=True, returns the request thread
+        """
+        url = self.configuration.host + "/v1/updateTaskStatus"
+        token = self.configuration.token
+        
+        headers = {
+            "Authorization": f"Bearer {token}",
+            "Content-Type": "application/json"
+        }
+
+        data = {
+            "bizMeta": body.biz_meta,
+            "callbackUrl": body.callback_url,
+            "logs": [{"createdTime":log.created_time, "log":log.log} for log in body.logs] if body.logs else [],
+            "outputData": body.output_data,
+            "reasonForIncompletion":body.reason_for_incompletion,
+            "status": body.status,
+            "taskId": body.task_id,
+            "workId": body.worker_id
+        }
+        try:
+            response = requests.post(url, headers=headers, json=data)
+            response.raise_for_status()
+            logging.debug(response.text)
+            if response.status_code == 200:
+                return True
+            else:
+                return False
+            
+        except requests.exceptions.RequestException as e:
+            logging.error(f"Failed to update task to AaaS: {str(e)}")
+            raise
diff --git a/omagent_core/engine/http/api/application_resource_api.py b/omagent_core/engine/http/api/application_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..578f6459b03d4d57d99325b2af9767bd188dcbd2
--- /dev/null
+++ b/omagent_core/engine/http/api/application_resource_api.py
@@ -0,0 +1,1498 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class ApplicationResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def add_role_to_application_user(
+        self, application_id, role, **kwargs
+    ):  # noqa: E501
+        """add_role_to_application_user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_role_to_application_user(application_id, role, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str role: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.add_role_to_application_user_with_http_info(
+                application_id, role, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.add_role_to_application_user_with_http_info(
+                application_id, role, **kwargs
+            )  # noqa: E501
+            return data
+
+    def add_role_to_application_user_with_http_info(
+        self, application_id, role, **kwargs
+    ):  # noqa: E501
+        """add_role_to_application_user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_role_to_application_user_with_http_info(application_id, role, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str role: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["application_id", "role"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method add_role_to_application_user" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'application_id' is set
+        if "application_id" not in params or params["application_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `application_id` when calling `add_role_to_application_user`"
+            )  # noqa: E501
+        # verify the required parameter 'role' is set
+        if "role" not in params or params["role"] is None:
+            raise ValueError(
+                "Missing the required parameter `role` when calling `add_role_to_application_user`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "application_id" in params:
+            path_params["applicationId"] = params["application_id"]  # noqa: E501
+        if "role" in params:
+            path_params["role"] = params["role"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{applicationId}/roles/{role}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def create_access_key(self, id, **kwargs):  # noqa: E501
+        """Create an access key for an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_access_key(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.create_access_key_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.create_access_key_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def create_access_key_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Create an access key for an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_access_key_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method create_access_key" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `create_access_key`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}/accessKeys",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def create_application(self, body, **kwargs):  # noqa: E501
+        """Create an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_application(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param CreateOrUpdateApplicationRequest body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.create_application_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.create_application_with_http_info(
+                body, **kwargs
+            )  # noqa: E501
+            return data
+
+    def create_application_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Create an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_application_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param CreateOrUpdateApplicationRequest body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method create_application" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `create_application`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_access_key(self, application_id, key_id, **kwargs):  # noqa: E501
+        """Delete an access key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_access_key(application_id, key_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str key_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_access_key_with_http_info(
+                application_id, key_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_access_key_with_http_info(
+                application_id, key_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_access_key_with_http_info(
+        self, application_id, key_id, **kwargs
+    ):  # noqa: E501
+        """Delete an access key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_access_key_with_http_info(application_id, key_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str key_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["application_id", "key_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_access_key" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'application_id' is set
+        if "application_id" not in params or params["application_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `application_id` when calling `delete_access_key`"
+            )  # noqa: E501
+        # verify the required parameter 'key_id' is set
+        if "key_id" not in params or params["key_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `key_id` when calling `delete_access_key`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "application_id" in params:
+            path_params["applicationId"] = params["application_id"]  # noqa: E501
+        if "key_id" in params:
+            path_params["keyId"] = params["key_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{applicationId}/accessKeys/{keyId}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_application(self, id, **kwargs):  # noqa: E501
+        """Delete an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_application(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_application_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.delete_application_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def delete_application_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Delete an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_application_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_application" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `delete_application`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_access_keys(self, id, **kwargs):  # noqa: E501
+        """Get application's access keys  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_access_keys(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_access_keys_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_access_keys_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def get_access_keys_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Get application's access keys  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_access_keys_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_access_keys" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `get_access_keys`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}/accessKeys",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_application(self, id, **kwargs):  # noqa: E501
+        """Get an application by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_application(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_application_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_application_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def get_application_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Get an application by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_application_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_application" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `get_application`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def list_applications(self, **kwargs):  # noqa: E501
+        """Get all applications  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_applications(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[ConductorApplication]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.list_applications_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.list_applications_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def list_applications_with_http_info(self, **kwargs):  # noqa: E501
+        """Get all applications  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_applications_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[ConductorApplication]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_applications" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[ConductorApplication]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def remove_role_from_application_user(
+        self, application_id, role, **kwargs
+    ):  # noqa: E501
+        """remove_role_from_application_user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_role_from_application_user(application_id, role, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str role: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.remove_role_from_application_user_with_http_info(
+                application_id, role, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.remove_role_from_application_user_with_http_info(
+                application_id, role, **kwargs
+            )  # noqa: E501
+            return data
+
+    def remove_role_from_application_user_with_http_info(
+        self, application_id, role, **kwargs
+    ):  # noqa: E501
+        """remove_role_from_application_user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_role_from_application_user_with_http_info(application_id, role, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str role: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["application_id", "role"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method remove_role_from_application_user" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'application_id' is set
+        if "application_id" not in params or params["application_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `application_id` when calling `remove_role_from_application_user`"
+            )  # noqa: E501
+        # verify the required parameter 'role' is set
+        if "role" not in params or params["role"] is None:
+            raise ValueError(
+                "Missing the required parameter `role` when calling `remove_role_from_application_user`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "application_id" in params:
+            path_params["applicationId"] = params["application_id"]  # noqa: E501
+        if "role" in params:
+            path_params["role"] = params["role"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{applicationId}/roles/{role}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def toggle_access_key_status(self, application_id, key_id, **kwargs):  # noqa: E501
+        """Toggle the status of an access key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.toggle_access_key_status(application_id, key_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str key_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.toggle_access_key_status_with_http_info(
+                application_id, key_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.toggle_access_key_status_with_http_info(
+                application_id, key_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def toggle_access_key_status_with_http_info(
+        self, application_id, key_id, **kwargs
+    ):  # noqa: E501
+        """Toggle the status of an access key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.toggle_access_key_status_with_http_info(application_id, key_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str application_id: (required)
+        :param str key_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["application_id", "key_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method toggle_access_key_status" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'application_id' is set
+        if "application_id" not in params or params["application_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `application_id` when calling `toggle_access_key_status`"
+            )  # noqa: E501
+        # verify the required parameter 'key_id' is set
+        if "key_id" not in params or params["key_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `key_id` when calling `toggle_access_key_status`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "application_id" in params:
+            path_params["applicationId"] = params["application_id"]  # noqa: E501
+        if "key_id" in params:
+            path_params["keyId"] = params["key_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{applicationId}/accessKeys/{keyId}/status",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_application(self, body, id, **kwargs):  # noqa: E501
+        """Update an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_application(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param CreateOrUpdateApplicationRequest body: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_application_with_http_info(
+                body, id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.update_application_with_http_info(
+                body, id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def update_application_with_http_info(self, body, id, **kwargs):  # noqa: E501
+        """Update an application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_application_with_http_info(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param CreateOrUpdateApplicationRequest body: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_application" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_application`"
+            )  # noqa: E501
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `update_application`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_tags_for_application(self, body, id, **kwargs):  # noqa: E501
+        """Put a tag to application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_application(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_tags_for_application_with_http_info(
+                body, id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.put_tags_for_application_with_http_info(
+                body, id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def put_tags_for_application_with_http_info(self, body, id, **kwargs):  # noqa: E501
+        """Put a tag to application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_application_with_http_info(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_tag_for_application" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_tag_for_application`"
+            )  # noqa: E501
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `put_tag_for_application`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_tags_for_application(self, id, **kwargs):  # noqa: E501
+        """Get tags by application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_application(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_tags_for_application_with_http_info(
+                id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_tags_for_application_with_http_info(
+                id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_tags_for_application_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Get tags by application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_application_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_tags_for_application" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `get_tags_for_application`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_tags_for_application(self, body, id, **kwargs):  # noqa: E501
+        """Delete a tag for application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_application(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_tags_for_application_with_http_info(
+                body, id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_tags_for_application_with_http_info(
+                body, id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_tags_for_application_with_http_info(
+        self, body, id, **kwargs
+    ):  # noqa: E501
+        """Delete a tag for application  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_application_with_http_info(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_tag_for_application" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_tag_for_application`"
+            )  # noqa: E501
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `delete_tag_for_application`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/applications/{id}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/authorization_resource_api.py b/omagent_core/engine/http/api/authorization_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdb4819bbb5ebd3f43d2d5d63926db320554a466
--- /dev/null
+++ b/omagent_core/engine/http/api/authorization_resource_api.py
@@ -0,0 +1,338 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class AuthorizationResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def get_permissions(self, type, id, **kwargs):  # noqa: E501
+        """Get the access that have been granted over the given object  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_permissions(type, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str type: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_permissions_with_http_info(type, id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_permissions_with_http_info(
+                type, id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_permissions_with_http_info(self, type, id, **kwargs):  # noqa: E501
+        """Get the access that have been granted over the given object  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_permissions_with_http_info(type, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str type: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["type", "id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_permissions" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'type' is set
+        if "type" not in params or params["type"] is None:
+            raise ValueError(
+                "Missing the required parameter `type` when calling `get_permissions`"
+            )  # noqa: E501
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `get_permissions`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "type" in params:
+            path_params["type"] = params["type"]  # noqa: E501
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/auth/authorization/{type}/{id}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def grant_permissions(self, body, **kwargs):  # noqa: E501
+        """Grant access to a user over the target  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.grant_permissions(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param AuthorizationRequest body: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.grant_permissions_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.grant_permissions_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def grant_permissions_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Grant access to a user over the target  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.grant_permissions_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param AuthorizationRequest body: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method grant_permissions" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `grant_permissions`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/auth/authorization",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Response",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def remove_permissions(self, body, **kwargs):  # noqa: E501
+        """Remove user's access over the target  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_permissions(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param AuthorizationRequest body: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.remove_permissions_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.remove_permissions_with_http_info(
+                body, **kwargs
+            )  # noqa: E501
+            return data
+
+    def remove_permissions_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Remove user's access over the target  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_permissions_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param AuthorizationRequest body: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method remove_permissions" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `remove_permissions`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/auth/authorization",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Response",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/event_resource_api.py b/omagent_core/engine/http/api/event_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b02923c7c1dbd248e52e76a1cd0f51fafc21b0b
--- /dev/null
+++ b/omagent_core/engine/http/api/event_resource_api.py
@@ -0,0 +1,943 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class EventResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def add_event_handler(self, body, **kwargs):  # noqa: E501
+        """Add a new event handler.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_event_handler(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param EventHandler body: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.add_event_handler_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.add_event_handler_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def add_event_handler_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Add a new event handler.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_event_handler_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param EventHandler body: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method add_event_handler" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `add_event_handler`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_queue_config(self, queue_type, queue_name, **kwargs):  # noqa: E501
+        """Delete queue config by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_queue_config(queue_type, queue_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str queue_type: (required)
+        :param str queue_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_queue_config_with_http_info(
+                queue_type, queue_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_queue_config_with_http_info(
+                queue_type, queue_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_queue_config_with_http_info(
+        self, queue_type, queue_name, **kwargs
+    ):  # noqa: E501
+        """Delete queue config by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_queue_config_with_http_info(queue_type, queue_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str queue_type: (required)
+        :param str queue_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["queue_type", "queue_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_queue_config" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'queue_type' is set
+        if "queue_type" not in params or params["queue_type"] is None:
+            raise ValueError(
+                "Missing the required parameter `queue_type` when calling `delete_queue_config`"
+            )  # noqa: E501
+        # verify the required parameter 'queue_name' is set
+        if "queue_name" not in params or params["queue_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `queue_name` when calling `delete_queue_config`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "queue_type" in params:
+            path_params["queueType"] = params["queue_type"]  # noqa: E501
+        if "queue_name" in params:
+            path_params["queueName"] = params["queue_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event/queue/config/{queueType}/{queueName}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_event_handlers(self, **kwargs):  # noqa: E501
+        """Get all the event handlers  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_event_handlers(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[EventHandler]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_event_handlers_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_event_handlers_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_event_handlers_with_http_info(self, **kwargs):  # noqa: E501
+        """Get all the event handlers  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_event_handlers_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[EventHandler]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_event_handlers" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[EventHandler]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_event_handlers_for_event(self, event, **kwargs):  # noqa: E501
+        """Get event handlers for a given event  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_event_handlers_for_event(event, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str event: (required)
+        :param bool active_only:
+        :return: list[EventHandler]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_event_handlers_for_event_with_http_info(
+                event, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_event_handlers_for_event_with_http_info(
+                event, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_event_handlers_for_event_with_http_info(
+        self, event, **kwargs
+    ):  # noqa: E501
+        """Get event handlers for a given event  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_event_handlers_for_event_with_http_info(event, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str event: (required)
+        :param bool active_only:
+        :return: list[EventHandler]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["event", "active_only"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_event_handlers_for_event" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'event' is set
+        if "event" not in params or params["event"] is None:
+            raise ValueError(
+                "Missing the required parameter `event` when calling `get_event_handlers_for_event`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "event" in params:
+            path_params["event"] = params["event"]  # noqa: E501
+
+        query_params = []
+        if "active_only" in params:
+            query_params.append(("activeOnly", params["active_only"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event/{event}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[EventHandler]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_queue_config(self, queue_type, queue_name, **kwargs):  # noqa: E501
+        """Get queue config by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_queue_config(queue_type, queue_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str queue_type: (required)
+        :param str queue_name: (required)
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_queue_config_with_http_info(
+                queue_type, queue_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_queue_config_with_http_info(
+                queue_type, queue_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_queue_config_with_http_info(
+        self, queue_type, queue_name, **kwargs
+    ):  # noqa: E501
+        """Get queue config by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_queue_config_with_http_info(queue_type, queue_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str queue_type: (required)
+        :param str queue_name: (required)
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["queue_type", "queue_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_queue_config" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'queue_type' is set
+        if "queue_type" not in params or params["queue_type"] is None:
+            raise ValueError(
+                "Missing the required parameter `queue_type` when calling `get_queue_config`"
+            )  # noqa: E501
+        # verify the required parameter 'queue_name' is set
+        if "queue_name" not in params or params["queue_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `queue_name` when calling `get_queue_config`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "queue_type" in params:
+            path_params["queueType"] = params["queue_type"]  # noqa: E501
+        if "queue_name" in params:
+            path_params["queueName"] = params["queue_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event/queue/config/{queueType}/{queueName}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, object)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_queue_names(self, **kwargs):  # noqa: E501
+        """Get all queue configs  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_queue_names(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, str)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_queue_names_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_queue_names_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_queue_names_with_http_info(self, **kwargs):  # noqa: E501
+        """Get all queue configs  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_queue_names_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, str)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_queue_names" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event/queue/config",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, str)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_queue_config(self, body, queue_type, queue_name, **kwargs):  # noqa: E501
+        """Create or update queue config by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_queue_config(body, queue_type, queue_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str queue_type: (required)
+        :param str queue_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_queue_config_with_http_info(
+                body, queue_type, queue_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.put_queue_config_with_http_info(
+                body, queue_type, queue_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def put_queue_config_with_http_info(
+        self, body, queue_type, queue_name, **kwargs
+    ):  # noqa: E501
+        """Create or update queue config by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_queue_config_with_http_info(body, queue_type, queue_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str queue_type: (required)
+        :param str queue_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "queue_type", "queue_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_queue_config" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_queue_config`"
+            )  # noqa: E501
+        # verify the required parameter 'queue_type' is set
+        if "queue_type" not in params or params["queue_type"] is None:
+            raise ValueError(
+                "Missing the required parameter `queue_type` when calling `put_queue_config`"
+            )  # noqa: E501
+        # verify the required parameter 'queue_name' is set
+        if "queue_name" not in params or params["queue_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `queue_name` when calling `put_queue_config`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "queue_type" in params:
+            path_params["queueType"] = params["queue_type"]  # noqa: E501
+        if "queue_name" in params:
+            path_params["queueName"] = params["queue_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event/queue/config/{queueType}/{queueName}",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def remove_event_handler_status(self, name, **kwargs):  # noqa: E501
+        """Remove an event handler  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_event_handler_status(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.remove_event_handler_status_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.remove_event_handler_status_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def remove_event_handler_status_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Remove an event handler  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_event_handler_status_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method remove_event_handler_status" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `remove_event_handler_status`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event/{name}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_event_handler(self, body, **kwargs):  # noqa: E501
+        """Update an existing event handler.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_event_handler(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param EventHandler body: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_event_handler_with_http_info(
+                body, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.update_event_handler_with_http_info(
+                body, **kwargs
+            )  # noqa: E501
+            return data
+
+    def update_event_handler_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Update an existing event handler.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_event_handler_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param EventHandler body: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_event_handler" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_event_handler`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/event",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/group_resource_api.py b/omagent_core/engine/http/api/group_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..f84ec56c13aff2cb687e41d55ea7eb70a1a63c6a
--- /dev/null
+++ b/omagent_core/engine/http/api/group_resource_api.py
@@ -0,0 +1,837 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class GroupResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def add_user_to_group(self, group_id, user_id, **kwargs):  # noqa: E501
+        """Add user to group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_user_to_group(group_id, user_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str group_id: (required)
+        :param str user_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.add_user_to_group_with_http_info(
+                group_id, user_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.add_user_to_group_with_http_info(
+                group_id, user_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def add_user_to_group_with_http_info(
+        self, group_id, user_id, **kwargs
+    ):  # noqa: E501
+        """Add user to group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_user_to_group_with_http_info(group_id, user_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str group_id: (required)
+        :param str user_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["group_id", "user_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method add_user_to_group" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'group_id' is set
+        if "group_id" not in params or params["group_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `group_id` when calling `add_user_to_group`"
+            )  # noqa: E501
+        # verify the required parameter 'user_id' is set
+        if "user_id" not in params or params["user_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `user_id` when calling `add_user_to_group`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "group_id" in params:
+            path_params["groupId"] = params["group_id"]  # noqa: E501
+        if "user_id" in params:
+            path_params["userId"] = params["user_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups/{groupId}/users/{userId}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_group(self, id, **kwargs):  # noqa: E501
+        """Delete a group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_group(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_group_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.delete_group_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def delete_group_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Delete a group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_group_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_group" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `delete_group`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups/{id}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Response",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_granted_permissions1(self, group_id, **kwargs):  # noqa: E501
+        """Get the permissions this group has over workflows and tasks  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_granted_permissions1(group_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str group_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_granted_permissions1_with_http_info(
+                group_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_granted_permissions1_with_http_info(
+                group_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_granted_permissions1_with_http_info(self, group_id, **kwargs):  # noqa: E501
+        """Get the permissions this group has over workflows and tasks  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_granted_permissions1_with_http_info(group_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str group_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["group_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_granted_permissions1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'group_id' is set
+        if "group_id" not in params or params["group_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `group_id` when calling `get_granted_permissions1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "group_id" in params:
+            path_params["groupId"] = params["group_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups/{groupId}/permissions",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_group(self, id, **kwargs):  # noqa: E501
+        """Get a group by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_group(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_group_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_group_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def get_group_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Get a group by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_group_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_group" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `get_group`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups/{id}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_users_in_group(self, id, **kwargs):  # noqa: E501
+        """Get all users in group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_users_in_group(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_users_in_group_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_users_in_group_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def get_users_in_group_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Get all users in group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_users_in_group_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_users_in_group" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `get_users_in_group`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups/{id}/users",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def list_groups(self, **kwargs):  # noqa: E501
+        """Get all groups  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_groups(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[Group]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.list_groups_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.list_groups_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def list_groups_with_http_info(self, **kwargs):  # noqa: E501
+        """Get all groups  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_groups_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[Group]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_groups" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[Group]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def remove_user_from_group(self, group_id, user_id, **kwargs):  # noqa: E501
+        """Remove user from group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_user_from_group(group_id, user_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str group_id: (required)
+        :param str user_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.remove_user_from_group_with_http_info(
+                group_id, user_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.remove_user_from_group_with_http_info(
+                group_id, user_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def remove_user_from_group_with_http_info(
+        self, group_id, user_id, **kwargs
+    ):  # noqa: E501
+        """Remove user from group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.remove_user_from_group_with_http_info(group_id, user_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str group_id: (required)
+        :param str user_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["group_id", "user_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method remove_user_from_group" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'group_id' is set
+        if "group_id" not in params or params["group_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `group_id` when calling `remove_user_from_group`"
+            )  # noqa: E501
+        # verify the required parameter 'user_id' is set
+        if "user_id" not in params or params["user_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `user_id` when calling `remove_user_from_group`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "group_id" in params:
+            path_params["groupId"] = params["group_id"]  # noqa: E501
+        if "user_id" in params:
+            path_params["userId"] = params["user_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups/{groupId}/users/{userId}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def upsert_group(self, body, id, **kwargs):  # noqa: E501
+        """Create or update a group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.upsert_group(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param UpsertGroupRequest body: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.upsert_group_with_http_info(body, id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.upsert_group_with_http_info(body, id, **kwargs)  # noqa: E501
+            return data
+
+    def upsert_group_with_http_info(self, body, id, **kwargs):  # noqa: E501
+        """Create or update a group  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.upsert_group_with_http_info(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param UpsertGroupRequest body: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method upsert_group" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `upsert_group`"
+            )  # noqa: E501
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `upsert_group`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/groups/{id}",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/integration_resource_api.py b/omagent_core/engine/http/api/integration_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b8bb11ae2ba2916b01a6918e18472a7e3befd10
--- /dev/null
+++ b/omagent_core/engine/http/api/integration_resource_api.py
@@ -0,0 +1,2454 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class IntegrationResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def associate_prompt_with_integration(
+        self, integration_provider, integration_name, prompt_name, **kwargs
+    ):  # noqa: E501
+        """Associate a Prompt Template with an Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.associate_prompt_with_integration(integration_provider, integration_name, prompt_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str integration_provider: (required)
+        :param str integration_name: (required)
+        :param str prompt_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.associate_prompt_with_integration_with_http_info(
+                integration_provider, integration_name, prompt_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.associate_prompt_with_integration_with_http_info(
+                integration_provider, integration_name, prompt_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def associate_prompt_with_integration_with_http_info(
+        self, integration_provider, integration_name, prompt_name, **kwargs
+    ):  # noqa: E501
+        """Associate a Prompt Template with an Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.associate_prompt_with_integration_with_http_info(integration_provider, integration_name, prompt_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str integration_provider: (required)
+        :param str integration_name: (required)
+        :param str prompt_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "integration_provider",
+            "integration_name",
+            "prompt_name",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method associate_prompt_with_integration" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'integration_provider' is set
+        if (
+            "integration_provider" not in params
+            or params["integration_provider"] is None
+        ):
+            raise ValueError(
+                "Missing the required parameter `integration_provider` when calling `associate_prompt_with_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `associate_prompt_with_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'prompt_name' is set
+        if "prompt_name" not in params or params["prompt_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `prompt_name` when calling `associate_prompt_with_integration`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "integration_provider" in params:
+            path_params["integration_provider"] = params[
+                "integration_provider"
+            ]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+        if "prompt_name" in params:
+            path_params["prompt_name"] = params["prompt_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{integration_provider}/integration/{integration_name}/prompt/{prompt_name}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_integration_api(self, name, integration_name, **kwargs):  # noqa: E501
+        """Delete an Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_integration_api(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_integration_api_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_integration_api_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_integration_api_with_http_info(
+        self, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Delete an Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_integration_api_with_http_info(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_integration_api" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_integration_api`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `delete_integration_api`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_integration_provider(self, name, **kwargs):  # noqa: E501
+        """Delete an Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_integration_provider(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_integration_provider_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Delete an Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_integration_provider_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_integration_provider" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_integration_provider`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_tag_for_integration(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Delete a tag for Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_integration(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_tag_for_integration_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_tag_for_integration_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_tag_for_integration_with_http_info(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Delete a tag for Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_integration_with_http_info(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_tag_for_integration" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_tag_for_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_tag_for_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `delete_tag_for_integration`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_tag_for_integration_provider(self, body, name, **kwargs):  # noqa: E501
+        """Delete a tag for Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_integration_provider(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_tag_for_integration_provider_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_tag_for_integration_provider_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_tag_for_integration_provider_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Delete a tag for Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_integration_provider_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_tag_for_integration_provider" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_tag_for_integration_provider`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_tag_for_integration_provider`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_integration_api(self, name, integration_name, **kwargs):  # noqa: E501
+        """Get Integration details  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_api(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: IntegrationApi
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_integration_api_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_integration_api_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_integration_api_with_http_info(
+        self, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Get Integration details  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_api_with_http_info(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: IntegrationApi
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_integration_api" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_integration_api`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `get_integration_api`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="IntegrationApi",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_integration_apis(self, name, **kwargs):  # noqa: E501
+        """Get Integrations of an Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_apis(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param bool active_only:
+        :return: list[IntegrationApi]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_integration_apis_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_integration_apis_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_integration_apis_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Get Integrations of an Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_apis_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param bool active_only:
+        :return: list[IntegrationApi]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "active_only"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_integration_apis" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_integration_apis`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "active_only" in params:
+            query_params.append(("activeOnly", params["active_only"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[IntegrationApi]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_integration_available_apis(self, name, **kwargs):  # noqa: E501
+        """Get Integrations Available for an Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_available_apis(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_integration_available_apis_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_integration_available_apis_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_integration_available_apis_with_http_info(
+        self, name, **kwargs
+    ):  # noqa: E501
+        """Get Integrations Available for an Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_available_apis_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_integration_available_apis" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_integration_available_apis`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/all",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[str]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_integration_provider(self, name, **kwargs):  # noqa: E501
+        """Get Integration provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_provider(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: Integration
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_integration_provider_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Get Integration provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_provider_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: Integration
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_integration_provider" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_integration_provider`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Integration",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_integration_provider_defs(self, **kwargs):  # noqa: E501
+        """Get Integration provider definitions  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_provider_defs(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[IntegrationDef]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_integration_provider_defs_with_http_info(
+                **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_integration_provider_defs_with_http_info(
+                **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_integration_provider_defs_with_http_info(self, **kwargs):  # noqa: E501
+        """Get Integration provider definitions  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_provider_defs_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[IntegrationDef]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_integration_provider_defs" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/def",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[IntegrationDef]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_integration_providers(self, **kwargs):  # noqa: E501
+        """Get all Integrations Providers  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_providers(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str type:
+        :param bool active_only:
+        :return: list[Integration]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_integration_providers_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_integration_providers_with_http_info(
+                **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_integration_providers_with_http_info(self, **kwargs):  # noqa: E501
+        """Get all Integrations Providers  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_integration_providers_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str type:
+        :param bool active_only:
+        :return: list[Integration]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["type", "active_only"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_integration_providers" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "type" in params:
+            query_params.append(("type", params["type"]))  # noqa: E501
+        if "active_only" in params:
+            query_params.append(("activeOnly", params["active_only"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[Integration]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_prompts_with_integration(
+        self, integration_provider, integration_name, **kwargs
+    ):  # noqa: E501
+        """Get the list of prompt templates associated with an integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_prompts_with_integration(integration_provider, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str integration_provider: (required)
+        :param str integration_name: (required)
+        :return: list[PromptTemplate]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_prompts_with_integration_with_http_info(
+                integration_provider, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_prompts_with_integration_with_http_info(
+                integration_provider, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_prompts_with_integration_with_http_info(
+        self, integration_provider, integration_name, **kwargs
+    ):  # noqa: E501
+        """Get the list of prompt templates associated with an integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_prompts_with_integration_with_http_info(integration_provider, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str integration_provider: (required)
+        :param str integration_name: (required)
+        :return: list[PromptTemplate]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["integration_provider", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_prompts_with_integration" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'integration_provider' is set
+        if (
+            "integration_provider" not in params
+            or params["integration_provider"] is None
+        ):
+            raise ValueError(
+                "Missing the required parameter `integration_provider` when calling `get_prompts_with_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `get_prompts_with_integration`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "integration_provider" in params:
+            path_params["integration_provider"] = params[
+                "integration_provider"
+            ]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{integration_provider}/integration/{integration_name}/prompt",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[PromptTemplate]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_providers_and_integrations(self, **kwargs):  # noqa: E501
+        """Get Integrations Providers and Integrations combo  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_providers_and_integrations(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str type:
+        :param bool active_only:
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_providers_and_integrations_with_http_info(
+                **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_providers_and_integrations_with_http_info(
+                **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_providers_and_integrations_with_http_info(self, **kwargs):  # noqa: E501
+        """Get Integrations Providers and Integrations combo  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_providers_and_integrations_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str type:
+        :param bool active_only:
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["type", "active_only"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_providers_and_integrations" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "type" in params:
+            query_params.append(("type", params["type"]))  # noqa: E501
+        if "active_only" in params:
+            query_params.append(("activeOnly", params["active_only"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/all",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[str]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_tags_for_integration(self, name, integration_name, **kwargs):  # noqa: E501
+        """Get tags by Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_integration(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_tags_for_integration_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_tags_for_integration_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_tags_for_integration_with_http_info(
+        self, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Get tags by Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_integration_with_http_info(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_tags_for_integration" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_tags_for_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `get_tags_for_integration`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_tags_for_integration_provider(self, name, **kwargs):  # noqa: E501
+        """Get tags by Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_integration_provider(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_tags_for_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_tags_for_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_tags_for_integration_provider_with_http_info(
+        self, name, **kwargs
+    ):  # noqa: E501
+        """Get tags by Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_integration_provider_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_tags_for_integration_provider" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_tags_for_integration_provider`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_token_usage_for_integration(
+        self, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Get Token Usage by Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_token_usage_for_integration(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: int
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_token_usage_for_integration_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_token_usage_for_integration_with_http_info(
+                name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_token_usage_for_integration_with_http_info(
+        self, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Get Token Usage by Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_token_usage_for_integration_with_http_info(name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: int
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_token_usage_for_integration" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_token_usage_for_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `get_token_usage_for_integration`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}/metrics",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="int",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_token_usage_for_integration_provider(self, name, **kwargs):  # noqa: E501
+        """Get Token Usage by Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_token_usage_for_integration_provider(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: dict(str, str)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_token_usage_for_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_token_usage_for_integration_provider_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_token_usage_for_integration_provider_with_http_info(
+        self, name, **kwargs
+    ):  # noqa: E501
+        """Get Token Usage by Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_token_usage_for_integration_provider_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: dict(str, str)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_token_usage_for_integration_provider" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_token_usage_for_integration_provider`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/metrics",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, str)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_tag_for_integration(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Put a tag to Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_integration(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_tag_for_integration_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.put_tag_for_integration_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def put_tag_for_integration_with_http_info(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Put a tag to Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_integration_with_http_info(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_tag_for_integration" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_tag_for_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `put_tag_for_integration`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `put_tag_for_integration`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_tag_for_integration_provider(self, body, name, **kwargs):  # noqa: E501
+        """Put a tag to Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_integration_provider(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_tag_for_integration_provider_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.put_tag_for_integration_provider_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def put_tag_for_integration_provider_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Put a tag to Integration Provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_integration_provider_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_tag_for_integration_provider" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_tag_for_integration_provider`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `put_tag_for_integration_provider`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def register_token_usage(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Register Token usage  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.register_token_usage(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.register_token_usage_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.register_token_usage_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def register_token_usage_with_http_info(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Register Token usage  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.register_token_usage_with_http_info(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method register_token_usage" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `register_token_usage`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `register_token_usage`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `register_token_usage`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}/metrics",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def save_integration_api(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Create or Update Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_integration_api(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param IntegrationApiUpdate body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.save_integration_api_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.save_integration_api_with_http_info(
+                body, name, integration_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def save_integration_api_with_http_info(
+        self, body, name, integration_name, **kwargs
+    ):  # noqa: E501
+        """Create or Update Integration  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_integration_api_with_http_info(body, name, integration_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param IntegrationApiUpdate body: (required)
+        :param str name: (required)
+        :param str integration_name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name", "integration_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method save_integration_api" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `save_integration_api`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `save_integration_api`"
+            )  # noqa: E501
+        # verify the required parameter 'integration_name' is set
+        if "integration_name" not in params or params["integration_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `integration_name` when calling `save_integration_api`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "integration_name" in params:
+            path_params["integration_name"] = params["integration_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}/integration/{integration_name}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def save_integration_provider(self, body, name, **kwargs):  # noqa: E501
+        """Create or Update Integration provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_integration_provider(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param IntegrationUpdate body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.save_integration_provider_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.save_integration_provider_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def save_integration_provider_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Create or Update Integration provider  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_integration_provider_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param IntegrationUpdate body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method save_integration_provider" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `save_integration_provider`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `save_integration_provider`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/integrations/provider/{name}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/metadata_resource_api.py b/omagent_core/engine/http/api/metadata_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..b1ac33920a7882f6c87de24c22ea6d8dcad38412
--- /dev/null
+++ b/omagent_core/engine/http/api/metadata_resource_api.py
@@ -0,0 +1,1356 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class MetadataResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def create(self, body, **kwargs):  # noqa: E501
+        """Create a new workflow definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowDef body: (required)
+        :param bool overwrite:
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.create_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.create_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def create_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Create a new workflow definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowDef body: (required)
+        :param bool overwrite:
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "overwrite"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method create" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `create`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "overwrite" in params:
+            query_params.append(("overwrite", params["overwrite"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def create_workflow_metadata(self, body, name, **kwargs):  # noqa: E501
+        """Store the metadata associated with workflow.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_workflow_metadata(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowTag body: (required)
+        :param str name: (required)
+        :param int version:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.create_workflow_metadata_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.create_workflow_metadata_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def create_workflow_metadata_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Store the metadata associated with workflow.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.create_workflow_metadata_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowTag body: (required)
+        :param str name: (required)
+        :param int version:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name", "version"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method create_workflow_metadata" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `create_workflow_metadata`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `create_workflow_metadata`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/tags/workflow/{name}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_workflow_metadata(self, name, version, **kwargs):  # noqa: E501
+        """Store the metadata associated with workflow.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_workflow_metadata(name, version, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_workflow_metadata_with_http_info(
+                name, version, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_workflow_metadata_with_http_info(
+                name, version, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_workflow_metadata_with_http_info(
+        self, name, version, **kwargs
+    ):  # noqa: E501
+        """Store the metadata associated with workflow.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_workflow_metadata_with_http_info(name, version, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "version"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_workflow_metadata" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_workflow_metadata`"
+            )  # noqa: E501
+        # verify the required parameter 'version' is set
+        if "version" not in params or params["version"] is None:
+            raise ValueError(
+                "Missing the required parameter `version` when calling `delete_workflow_metadata`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/tags/workflow/{name}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get(self, name, **kwargs):  # noqa: E501
+        """Retrieves workflow definition along with blueprint  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :return: WorkflowDef
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_with_http_info(name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_with_http_info(name, **kwargs)  # noqa: E501
+            return data
+
+    def get_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Retrieves workflow definition along with blueprint  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :return: WorkflowDef
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "version"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method get" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow/{name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="WorkflowDef",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_all_workflows(self, **kwargs):  # noqa: E501
+        """Retrieves all workflow definition along with blueprint  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_all_workflows(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str access:
+        :return: list[WorkflowDef]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_all_workflows_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_all_workflows_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_all_workflows_with_http_info(self, **kwargs):  # noqa: E501
+        """Retrieves all workflow definition along with blueprint  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_all_workflows_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str access:
+        :return: list[WorkflowDef]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["access"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_all_workflows" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "access" in params:
+            query_params.append(("access", params["access"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[WorkflowDef]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_task_def(self, tasktype, **kwargs):  # noqa: E501
+        """Gets the task definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_def(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :return: TaskDef
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_task_def_with_http_info(tasktype, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_task_def_with_http_info(tasktype, **kwargs)  # noqa: E501
+            return data
+
+    def get_task_def_with_http_info(self, tasktype, **kwargs):  # noqa: E501
+        """Gets the task definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_def_with_http_info(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :return: TaskDef
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["tasktype"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_task_def" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'tasktype' is set
+        if "tasktype" not in params or params["tasktype"] is None:
+            raise ValueError(
+                "Missing the required parameter `tasktype` when calling `get_task_def`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "tasktype" in params:
+            path_params["tasktype"] = params["tasktype"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/taskdefs/{tasktype}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="TaskDef",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_task_defs(self, **kwargs):  # noqa: E501
+        """Gets all task definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_defs(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str access:
+        :return: list[TaskDef]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_task_defs_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_task_defs_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_task_defs_with_http_info(self, **kwargs):  # noqa: E501
+        """Gets all task definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_defs_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str access:
+        :return: list[TaskDef]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["access"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_task_defs" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "access" in params:
+            query_params.append(("access", params["access"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/taskdefs",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TaskDef]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_workflow_metadata(self, name, **kwargs):  # noqa: E501
+        """Store the metadata associated with workflow.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflow_metadata(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :return: WorkflowTag
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_workflow_metadata_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_workflow_metadata_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_workflow_metadata_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Store the metadata associated with workflow.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflow_metadata_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :return: WorkflowTag
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "version"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_workflow_metadata" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_workflow_metadata`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/tags/workflow/{name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="WorkflowTag",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def register_task_def(self, body, **kwargs):  # noqa: E501
+        """Create or update task definition(s)  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.register_task_def(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TaskDef] body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.register_task_def_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.register_task_def_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def register_task_def_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Create or update task definition(s)  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.register_task_def_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TaskDef] body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method register_task_def" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `register_task_def`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/taskdefs",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def unregister_task_def(self, tasktype, **kwargs):  # noqa: E501
+        """Remove a task definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.unregister_task_def(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.unregister_task_def_with_http_info(
+                tasktype, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.unregister_task_def_with_http_info(
+                tasktype, **kwargs
+            )  # noqa: E501
+            return data
+
+    def unregister_task_def_with_http_info(self, tasktype, **kwargs):  # noqa: E501
+        """Remove a task definition  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.unregister_task_def_with_http_info(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["tasktype"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method unregister_task_def" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'tasktype' is set
+        if "tasktype" not in params or params["tasktype"] is None:
+            raise ValueError(
+                "Missing the required parameter `tasktype` when calling `unregister_task_def`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "tasktype" in params:
+            path_params["tasktype"] = params["tasktype"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/taskdefs/{tasktype}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def unregister_workflow_def(self, name, version, **kwargs):  # noqa: E501
+        """Removes workflow definition. It does not remove workflows associated with the definition.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.unregister_workflow_def(name, version, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.unregister_workflow_def_with_http_info(
+                name, version, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.unregister_workflow_def_with_http_info(
+                name, version, **kwargs
+            )  # noqa: E501
+            return data
+
+    def unregister_workflow_def_with_http_info(
+        self, name, version, **kwargs
+    ):  # noqa: E501
+        """Removes workflow definition. It does not remove workflows associated with the definition.  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.unregister_workflow_def_with_http_info(name, version, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "version"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method unregister_workflow_def" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `unregister_workflow_def`"
+            )  # noqa: E501
+        # verify the required parameter 'version' is set
+        if "version" not in params or params["version"] is None:
+            raise ValueError(
+                "Missing the required parameter `version` when calling `unregister_workflow_def`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "version" in params:
+            path_params["version"] = params["version"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow/{name}/{version}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update1(self, body, **kwargs):  # noqa: E501
+        """Create or update workflow definition(s)  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update1(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[WorkflowDef] body: (required)
+        :param bool overwrite:
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update1_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.update1_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def update1_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Create or update workflow definition(s)  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update1_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[WorkflowDef] body: (required)
+        :param bool overwrite:
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "overwrite"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method update1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "overwrite" in params:
+            query_params.append(("overwrite", params["overwrite"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_task_def(self, body, **kwargs):  # noqa: E501
+        """Update an existing task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task_def(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TaskDef body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_task_def_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.update_task_def_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def update_task_def_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Update an existing task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task_def_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TaskDef body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_task_def" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_task_def`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/taskdefs",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/prompt_resource_api.py b/omagent_core/engine/http/api/prompt_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..414cfc3e4ff95b1b9a3d5bd47baac78619ed0d05
--- /dev/null
+++ b/omagent_core/engine/http/api/prompt_resource_api.py
@@ -0,0 +1,882 @@
+# coding: utf-8
+
+"""
+    Orkes Conductor API Server
+
+    Orkes Conductor API Server  # noqa: E501
+
+    OpenAPI spec version: v2
+
+    Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class PromptResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def delete_message_template(self, name, **kwargs):  # noqa: E501
+        """Delete Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_message_template(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_message_template_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_message_template_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_message_template_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Delete Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_message_template_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_message_template" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_message_template`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts/{name}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_tag_for_prompt_template(self, body, name, **kwargs):  # noqa: E501
+        """Delete a tag for Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_prompt_template(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_tag_for_prompt_template_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_tag_for_prompt_template_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_tag_for_prompt_template_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Delete a tag for Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_prompt_template_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_tag_for_prompt_template" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_tag_for_prompt_template`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_tag_for_prompt_template`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts/{name}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_message_template(self, name, **kwargs):  # noqa: E501
+        """Get Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_message_template(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: PromptTemplate
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_message_template_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_message_template_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_message_template_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Get Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_message_template_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: PromptTemplate
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_message_template" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_message_template`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts/{name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="PromptTemplate",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_message_templates(self, **kwargs):  # noqa: E501
+        """Get Templates  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_message_templates(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[PromptTemplate]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_message_templates_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_message_templates_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_message_templates_with_http_info(self, **kwargs):  # noqa: E501
+        """Get Templates  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_message_templates_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[PromptTemplate]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_message_templates" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[PromptTemplate]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_tags_for_prompt_template(self, name, **kwargs):  # noqa: E501
+        """Get tags by Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_prompt_template(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_tags_for_prompt_template_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_tags_for_prompt_template_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_tags_for_prompt_template_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Get tags by Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_prompt_template_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_tags_for_prompt_template" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_tags_for_prompt_template`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts/{name}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_tag_for_prompt_template(self, body, name, **kwargs):  # noqa: E501
+        """Put a tag to Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_prompt_template(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_tag_for_prompt_template_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.put_tag_for_prompt_template_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def put_tag_for_prompt_template_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Put a tag to Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_prompt_template_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_tag_for_prompt_template" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_tag_for_prompt_template`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `put_tag_for_prompt_template`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts/{name}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def save_message_template(self, body, description, name, **kwargs):  # noqa: E501
+        """Create or Update Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_message_template(body, description, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str description: (required)
+        :param str name: (required)
+        :param list[str] models:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.save_message_template_with_http_info(
+                body, description, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.save_message_template_with_http_info(
+                body, description, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def save_message_template_with_http_info(
+        self, body, description, name, **kwargs
+    ):  # noqa: E501
+        """Create or Update Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_message_template_with_http_info(body, description, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str description: (required)
+        :param str name: (required)
+        :param list[str] models:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "description", "name", "models"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method save_message_template" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `save_message_template`"
+            )  # noqa: E501
+        # verify the required parameter 'description' is set
+        if "description" not in params or params["description"] is None:
+            raise ValueError(
+                "Missing the required parameter `description` when calling `save_message_template`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `save_message_template`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "description" in params:
+            query_params.append(("description", params["description"]))  # noqa: E501
+        if "models" in params:
+            query_params.append(("models", params["models"]))  # noqa: E501
+            collection_formats["models"] = "multi"  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts/{name}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def test_message_template(self, body, **kwargs):  # noqa: E501
+        """Test Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.test_message_template(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param PromptTemplateTestRequest body: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.test_message_template_with_http_info(
+                body, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.test_message_template_with_http_info(
+                body, **kwargs
+            )  # noqa: E501
+            return data
+
+    def test_message_template_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Test Prompt Template  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.test_message_template_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param PromptTemplateTestRequest body: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method test_message_template" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `test_message_template`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/prompts/test",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="str",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/scheduler_resource_api.py b/omagent_core/engine/http/api/scheduler_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..9aab440e3156d0bc9aeb9a99b43fdbc7c1be5f3d
--- /dev/null
+++ b/omagent_core/engine/http/api/scheduler_resource_api.py
@@ -0,0 +1,1518 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class SchedulerResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def delete_schedule(self, name, **kwargs):  # noqa: E501
+        """Deletes an existing workflow schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_schedule(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_schedule_with_http_info(name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.delete_schedule_with_http_info(name, **kwargs)  # noqa: E501
+            return data
+
+    def delete_schedule_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Deletes an existing workflow schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_schedule_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules/{name}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_all_schedules(self, **kwargs):  # noqa: E501
+        """Get all existing workflow schedules and optionally filter by workflow name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_all_schedules(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_name:
+        :return: list[WorkflowSchedule]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_all_schedules_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_all_schedules_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_all_schedules_with_http_info(self, **kwargs):  # noqa: E501
+        """Get all existing workflow schedules and optionally filter by workflow name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_all_schedules_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_name:
+        :return: list[WorkflowSchedule]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_all_schedules" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "workflow_name" in params:
+            query_params.append(("workflowName", params["workflow_name"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[WorkflowSchedule]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_next_few_schedules(self, cron_expression, **kwargs):  # noqa: E501
+        """Get list of the next x (default 3, max 5) execution times for a scheduler  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_next_few_schedules(cron_expression, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str cron_expression: (required)
+        :param int schedule_start_time:
+        :param int schedule_end_time:
+        :param int limit:
+        :return: list[int]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_next_few_schedules_with_http_info(
+                cron_expression, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_next_few_schedules_with_http_info(
+                cron_expression, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_next_few_schedules_with_http_info(
+        self, cron_expression, **kwargs
+    ):  # noqa: E501
+        """Get list of the next x (default 3, max 5) execution times for a scheduler  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_next_few_schedules_with_http_info(cron_expression, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str cron_expression: (required)
+        :param int schedule_start_time:
+        :param int schedule_end_time:
+        :param int limit:
+        :return: list[int]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "cron_expression",
+            "schedule_start_time",
+            "schedule_end_time",
+            "limit",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_next_few_schedules" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'cron_expression' is set
+        if "cron_expression" not in params or params["cron_expression"] is None:
+            raise ValueError(
+                "Missing the required parameter `cron_expression` when calling `get_next_few_schedules`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "cron_expression" in params:
+            query_params.append(
+                ("cronExpression", params["cron_expression"])
+            )  # noqa: E501
+        if "schedule_start_time" in params:
+            query_params.append(
+                ("scheduleStartTime", params["schedule_start_time"])
+            )  # noqa: E501
+        if "schedule_end_time" in params:
+            query_params.append(
+                ("scheduleEndTime", params["schedule_end_time"])
+            )  # noqa: E501
+        if "limit" in params:
+            query_params.append(("limit", params["limit"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/nextFewSchedules",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[int]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_schedule(self, name, **kwargs):  # noqa: E501
+        """Get an existing workflow schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_schedule(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_schedule_with_http_info(name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_schedule_with_http_info(name, **kwargs)  # noqa: E501
+            return data
+
+    def get_schedule_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Get an existing workflow schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_schedule_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules/{name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def pause_all_schedules(self, **kwargs):  # noqa: E501
+        """Pause all scheduling in a single conductor server instance (for debugging only)  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_all_schedules(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.pause_all_schedules_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.pause_all_schedules_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def pause_all_schedules_with_http_info(self, **kwargs):  # noqa: E501
+        """Pause all scheduling in a single conductor server instance (for debugging only)  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_all_schedules_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method pause_all_schedules" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/admin/pause",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, object)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def pause_schedule(self, name, **kwargs):  # noqa: E501
+        """Pauses an existing schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_schedule(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.pause_schedule_with_http_info(name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.pause_schedule_with_http_info(name, **kwargs)  # noqa: E501
+            return data
+
+    def pause_schedule_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Pauses an existing schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_schedule_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method pause_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `pause_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules/{name}/pause",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def requeue_all_execution_records(self, **kwargs):  # noqa: E501
+        """Requeue all execution records  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.requeue_all_execution_records(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.requeue_all_execution_records_with_http_info(
+                **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.requeue_all_execution_records_with_http_info(
+                **kwargs
+            )  # noqa: E501
+            return data
+
+    def requeue_all_execution_records_with_http_info(self, **kwargs):  # noqa: E501
+        """Requeue all execution records  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.requeue_all_execution_records_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method requeue_all_execution_records" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/admin/requeue",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, object)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def resume_all_schedules(self, **kwargs):  # noqa: E501
+        """Resume all scheduling  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_all_schedules(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.resume_all_schedules_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.resume_all_schedules_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def resume_all_schedules_with_http_info(self, **kwargs):  # noqa: E501
+        """Resume all scheduling  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_all_schedules_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method resume_all_schedules" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/admin/resume",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, object)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def resume_schedule(self, name, **kwargs):  # noqa: E501
+        """Resume a paused schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_schedule(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.resume_schedule_with_http_info(name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.resume_schedule_with_http_info(name, **kwargs)  # noqa: E501
+            return data
+
+    def resume_schedule_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Resume a paused schedule by name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_schedule_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method resume_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `resume_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules/{name}/resume",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def save_schedule(self, body, **kwargs):  # noqa: E501
+        """Create or update a schedule for a specified workflow with a corresponding start workflow request  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_schedule(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param SaveScheduleRequest body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.save_schedule_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.save_schedule_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def save_schedule_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Create or update a schedule for a specified workflow with a corresponding start workflow request  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.save_schedule_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param SaveScheduleRequest body: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method save_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `save_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def search_v21(self, **kwargs):  # noqa: E501
+        """Search for workflows based on payload and other parameters  # noqa: E501
+
+        use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC. If order is not specified, defaults to ASC.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search_v21(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int start:
+        :param int size:
+        :param str sort:
+        :param str free_text:
+        :param str query:
+        :return: SearchResultWorkflowScheduleExecutionModel
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.search_v21_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.search_v21_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def search_v21_with_http_info(self, **kwargs):  # noqa: E501
+        """Search for workflows based on payload and other parameters  # noqa: E501
+
+        use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC. If order is not specified, defaults to ASC.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search_v21_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int start:
+        :param int size:
+        :param str sort:
+        :param str free_text:
+        :param str query:
+        :return: SearchResultWorkflowScheduleExecutionModel
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["start", "size", "sort", "free_text", "query"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method search_v21" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "start" in params:
+            query_params.append(("start", params["start"]))  # noqa: E501
+        if "size" in params:
+            query_params.append(("size", params["size"]))  # noqa: E501
+        if "sort" in params:
+            query_params.append(("sort", params["sort"]))  # noqa: E501
+        if "free_text" in params:
+            query_params.append(("freeText", params["free_text"]))  # noqa: E501
+        if "query" in params:
+            query_params.append(("query", params["query"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/search/executions",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="SearchResultWorkflowScheduleExecutionModel",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def test_timeout(self, **kwargs):  # noqa: E501
+        """Test timeout - do not use in production  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.test_timeout(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.test_timeout_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.test_timeout_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def test_timeout_with_http_info(self, **kwargs):  # noqa: E501
+        """Test timeout - do not use in production  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.test_timeout_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method test_timeout" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/test/timeout",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_tag_for_schedule(self, body, name, **kwargs):  # noqa: E501
+        """Put a tag to schedule  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_schedule(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_tag_for_schedule_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.put_tag_for_schedule_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def put_tag_for_schedule_with_http_info(self, body, name, **kwargs):  # noqa: E501
+        """Put a tag to schedule  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_schedule_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_tag_for_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_tag_for_schedule`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `put_tag_for_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules/{name}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_tags_for_schedule(self, name, **kwargs):  # noqa: E501
+        """Get tags by schedule  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_schedule(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_tags_for_schedule_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_tags_for_schedule_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_tags_for_schedule_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Get tags by schedule  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_for_schedule_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_tags_for_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_tags_for_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules/{name}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_tag_for_schedule(self, body, name, **kwargs):  # noqa: E501
+        """Delete a tag for schedule  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_schedule(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_tag_for_schedule_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_tag_for_schedule_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_tag_for_schedule_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Delete a tag for schedule  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_schedule_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_tag_for_schedule" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_tag_for_schedule`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_tag_for_schedule`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/scheduler/schedules/{name}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/secret_resource_api.py b/omagent_core/engine/http/api/secret_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..e31bd889a184cc0ea4004d3fc8577c9d3dffc261
--- /dev/null
+++ b/omagent_core/engine/http/api/secret_resource_api.py
@@ -0,0 +1,1028 @@
+# coding: utf-8
+
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class SecretResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def delete_secret(self, key, **kwargs):  # noqa: E501
+        """Delete a secret value by key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_secret(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_secret_with_http_info(key, **kwargs)  # noqa: E501
+        else:
+            (data) = self.delete_secret_with_http_info(key, **kwargs)  # noqa: E501
+            return data
+
+    def delete_secret_with_http_info(self, key, **kwargs):  # noqa: E501
+        """Delete a secret value by key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_secret_with_http_info(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["key"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_secret" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'key' is set
+        if "key" not in params or params["key"] is None:
+            raise ValueError(
+                "Missing the required parameter `key` when calling `delete_secret`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "key" in params:
+            path_params["key"] = params["key"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets/{key}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_tag_for_secret(self, body, key, **kwargs):  # noqa: E501
+        """Delete tags of the secret  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_secret(body, key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str key: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_tag_for_secret_with_http_info(
+                body, key, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_tag_for_secret_with_http_info(
+                body, key, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_tag_for_secret_with_http_info(self, body, key, **kwargs):  # noqa: E501
+        """Delete tags of the secret  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_tag_for_secret_with_http_info(body, key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str key: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "key"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_tag_for_secret" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_tag_for_secret`"
+            )  # noqa: E501
+        # verify the required parameter 'key' is set
+        if "key" not in params or params["key"] is None:
+            raise ValueError(
+                "Missing the required parameter `key` when calling `delete_tag_for_secret`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "key" in params:
+            path_params["key"] = params["key"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets/{key}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_secret(self, key, **kwargs):  # noqa: E501
+        """Get secret value by key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_secret(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_secret_with_http_info(key, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_secret_with_http_info(key, **kwargs)  # noqa: E501
+            return data
+
+    def get_secret_with_http_info(self, key, **kwargs):  # noqa: E501
+        """Get secret value by key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_secret_with_http_info(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["key"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_secret" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'key' is set
+        if "key" not in params or params["key"] is None:
+            raise ValueError(
+                "Missing the required parameter `key` when calling `get_secret`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "key" in params:
+            path_params["key"] = params["key"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets/{key}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_tags(self, key, **kwargs):  # noqa: E501
+        """Get tags by secret  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_tags_with_http_info(key, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_tags_with_http_info(key, **kwargs)  # noqa: E501
+            return data
+
+    def get_tags_with_http_info(self, key, **kwargs):  # noqa: E501
+        """Get tags by secret  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags_with_http_info(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["key"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_tags" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'key' is set
+        if "key" not in params or params["key"] is None:
+            raise ValueError(
+                "Missing the required parameter `key` when calling `get_tags`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "key" in params:
+            path_params["key"] = params["key"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets/{key}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def list_all_secret_names(self, **kwargs):  # noqa: E501
+        """List all secret names  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_all_secret_names(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.list_all_secret_names_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.list_all_secret_names_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def list_all_secret_names_with_http_info(self, **kwargs):  # noqa: E501
+        """List all secret names  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_all_secret_names_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_all_secret_names" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def list_secrets_that_user_can_grant_access_to(self, **kwargs):  # noqa: E501
+        """List all secret names user can grant access to  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_secrets_that_user_can_grant_access_to(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.list_secrets_that_user_can_grant_access_to_with_http_info(
+                **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.list_secrets_that_user_can_grant_access_to_with_http_info(
+                **kwargs
+            )  # noqa: E501
+            return data
+
+    def list_secrets_that_user_can_grant_access_to_with_http_info(
+        self, **kwargs
+    ):  # noqa: E501
+        """List all secret names user can grant access to  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_secrets_that_user_can_grant_access_to_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_secrets_that_user_can_grant_access_to" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[str]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def list_secrets_with_tags_that_user_can_grant_access_to(
+        self, **kwargs
+    ):  # noqa: E501
+        """List all secret names along with tags user can grant access to  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_secrets_with_tags_that_user_can_grant_access_to(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[ExtendedSecret]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.list_secrets_with_tags_that_user_can_grant_access_to_with_http_info(
+                **kwargs
+            )  # noqa: E501
+        else:
+            (data) = (
+                self.list_secrets_with_tags_that_user_can_grant_access_to_with_http_info(
+                    **kwargs
+                )
+            )  # noqa: E501
+            return data
+
+    def list_secrets_with_tags_that_user_can_grant_access_to_with_http_info(
+        self, **kwargs
+    ):  # noqa: E501
+        """List all secret names along with tags user can grant access to  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_secrets_with_tags_that_user_can_grant_access_to_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[ExtendedSecret]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_secrets_with_tags_that_user_can_grant_access_to"
+                    % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets-v2",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[ExtendedSecret]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_secret(self, body, key, **kwargs):  # noqa: E501
+        """Put a secret value by key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_secret(body, key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_secret_with_http_info(body, key, **kwargs)  # noqa: E501
+        else:
+            (data) = self.put_secret_with_http_info(body, key, **kwargs)  # noqa: E501
+            return data
+
+    def put_secret_with_http_info(self, body, key, **kwargs):  # noqa: E501
+        """Put a secret value by key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_secret_with_http_info(body, key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "key"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_secret" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_secret`"
+            )  # noqa: E501
+        # verify the required parameter 'key' is set
+        if "key" not in params or params["key"] is None:
+            raise ValueError(
+                "Missing the required parameter `key` when calling `put_secret`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "key" in params:
+            path_params["key"] = params["key"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets/{key}",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def put_tag_for_secret(self, body, key, **kwargs):  # noqa: E501
+        """Tag a secret  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_secret(body, key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str key: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.put_tag_for_secret_with_http_info(
+                body, key, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.put_tag_for_secret_with_http_info(
+                body, key, **kwargs
+            )  # noqa: E501
+            return data
+
+    def put_tag_for_secret_with_http_info(self, body, key, **kwargs):  # noqa: E501
+        """Tag a secret  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.put_tag_for_secret_with_http_info(body, key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str key: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "key"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method put_tag_for_secret" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `put_tag_for_secret`"
+            )  # noqa: E501
+        # verify the required parameter 'key' is set
+        if "key" not in params or params["key"] is None:
+            raise ValueError(
+                "Missing the required parameter `key` when calling `put_tag_for_secret`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "key" in params:
+            path_params["key"] = params["key"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets/{key}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def secret_exists(self, key, **kwargs):  # noqa: E501
+        """Check if secret exists  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.secret_exists(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.secret_exists_with_http_info(key, **kwargs)  # noqa: E501
+        else:
+            (data) = self.secret_exists_with_http_info(key, **kwargs)  # noqa: E501
+            return data
+
+    def secret_exists_with_http_info(self, key, **kwargs):  # noqa: E501
+        """Check if secret exists  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.secret_exists_with_http_info(key, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str key: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["key"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method secret_exists" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'key' is set
+        if "key" not in params or params["key"] is None:
+            raise ValueError(
+                "Missing the required parameter `key` when calling `secret_exists`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "key" in params:
+            path_params["key"] = params["key"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/secrets/{key}/exists",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/task_resource_api.py b/omagent_core/engine/http/api/task_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..0755744c16fcc05a4b1a5659ab4c7e98f36a3a10
--- /dev/null
+++ b/omagent_core/engine/http/api/task_resource_api.py
@@ -0,0 +1,1839 @@
+from __future__ import absolute_import
+
+import logging
+import re  # noqa: F401
+import socket
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class TaskResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def all(self, **kwargs):  # noqa: E501
+        """Get the details about each queue  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.all(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, int)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.all_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.all_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def all_with_http_info(self, **kwargs):  # noqa: E501
+        """Get the details about each queue  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.all_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, int)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method all" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/queue/all",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, int)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def all_verbose(self, **kwargs):  # noqa: E501
+        """Get the details about each queue  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.all_verbose(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, dict(str, dict(str, int)))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.all_verbose_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.all_verbose_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def all_verbose_with_http_info(self, **kwargs):  # noqa: E501
+        """Get the details about each queue  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.all_verbose_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: dict(str, dict(str, dict(str, int)))
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method all_verbose" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/queue/all/verbose",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, dict(str, dict(str, int)))",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def batch_poll(self, tasktype, **kwargs):  # noqa: E501
+        """Batch poll for a task of a certain type  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.batch_poll(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :param str workerid:
+        :param str domain:
+        :param int count:
+        :param int timeout:
+        :return: list[Task]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.batch_poll_with_http_info(tasktype, **kwargs)  # noqa: E501
+        else:
+            (data) = self.batch_poll_with_http_info(tasktype, **kwargs)  # noqa: E501
+            return data
+
+    def batch_poll_with_http_info(self, tasktype, **kwargs):  # noqa: E501
+        """Batch poll for a task of a certain type  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.batch_poll_with_http_info(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :param str workerid:
+        :param str domain:
+        :param int count:
+        :param int timeout:
+        :return: list[Task]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "tasktype",
+            "workerid",
+            "domain",
+            "count",
+            "timeout",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method batch_poll" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'tasktype' is set
+        if "tasktype" not in params or params["tasktype"] is None:
+            raise ValueError(
+                "Missing the required parameter `tasktype` when calling `batch_poll`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "tasktype" in params:
+            path_params["tasktype"] = params["tasktype"]  # noqa: E501
+
+        query_params = []
+        if "workerid" in params:
+            query_params.append(("workerid", params["workerid"]))  # noqa: E501
+        if "domain" in params:
+            query_params.append(("domain", params["domain"]))  # noqa: E501
+        if "count" in params:
+            query_params.append(("count", params["count"]))  # noqa: E501
+        if "timeout" in params:
+            query_params.append(("timeout", params["timeout"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/poll/batch/{tasktype}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[Task]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_all_poll_data(self, **kwargs):  # noqa: E501
+        """Get the last poll data for all task types  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_all_poll_data(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[PollData]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_all_poll_data_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_all_poll_data_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_all_poll_data_with_http_info(self, **kwargs):  # noqa: E501
+        """Get the last poll data for all task types  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_all_poll_data_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[PollData]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_all_poll_data" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/queue/polldata/all",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[PollData]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_external_storage_location1(
+        self, path, operation, payload_type, **kwargs
+    ):  # noqa: E501
+        """Get the external uri where the task payload is to be stored  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_external_storage_location1(path, operation, payload_type, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str path: (required)
+        :param str operation: (required)
+        :param str payload_type: (required)
+        :return: ExternalStorageLocation
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_external_storage_location1_with_http_info(
+                path, operation, payload_type, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_external_storage_location1_with_http_info(
+                path, operation, payload_type, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_external_storage_location1_with_http_info(
+        self, path, operation, payload_type, **kwargs
+    ):  # noqa: E501
+        """Get the external uri where the task payload is to be stored  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_external_storage_location1_with_http_info(path, operation, payload_type, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str path: (required)
+        :param str operation: (required)
+        :param str payload_type: (required)
+        :return: ExternalStorageLocation
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["path", "operation", "payload_type"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_external_storage_location1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'path' is set
+        if "path" not in params or params["path"] is None:
+            raise ValueError(
+                "Missing the required parameter `path` when calling `get_external_storage_location1`"
+            )  # noqa: E501
+        # verify the required parameter 'operation' is set
+        if "operation" not in params or params["operation"] is None:
+            raise ValueError(
+                "Missing the required parameter `operation` when calling `get_external_storage_location1`"
+            )  # noqa: E501
+        # verify the required parameter 'payload_type' is set
+        if "payload_type" not in params or params["payload_type"] is None:
+            raise ValueError(
+                "Missing the required parameter `payload_type` when calling `get_external_storage_location1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "path" in params:
+            query_params.append(("path", params["path"]))  # noqa: E501
+        if "operation" in params:
+            query_params.append(("operation", params["operation"]))  # noqa: E501
+        if "payload_type" in params:
+            query_params.append(("payloadType", params["payload_type"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/externalstoragelocation",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="ExternalStorageLocation",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_poll_data(self, task_type, **kwargs):  # noqa: E501
+        """Get the last poll data for a given task type  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_poll_data(task_type, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_type: (required)
+        :return: list[PollData]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_poll_data_with_http_info(task_type, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_poll_data_with_http_info(
+                task_type, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_poll_data_with_http_info(self, task_type, **kwargs):  # noqa: E501
+        """Get the last poll data for a given task type  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_poll_data_with_http_info(task_type, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_type: (required)
+        :return: list[PollData]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["task_type"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_poll_data" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'task_type' is set
+        if "task_type" not in params or params["task_type"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_type` when calling `get_poll_data`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "task_type" in params:
+            query_params.append(("taskType", params["task_type"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/queue/polldata",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[PollData]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_task(self, task_id, **kwargs):  # noqa: E501
+        """Get task by Id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task(task_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_id: (required)
+        :return: Task
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_task_with_http_info(task_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_task_with_http_info(task_id, **kwargs)  # noqa: E501
+            return data
+
+    def get_task_with_http_info(self, task_id, **kwargs):  # noqa: E501
+        """Get task by Id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_with_http_info(task_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_id: (required)
+        :return: Task
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["task_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_task" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'task_id' is set
+        if "task_id" not in params or params["task_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_id` when calling `get_task`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_id" in params:
+            path_params["taskId"] = params["task_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/{taskId}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Task",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_task_logs(self, task_id, **kwargs):  # noqa: E501
+        """Get Task Execution Logs  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_logs(task_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_id: (required)
+        :return: list[TaskExecLog]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_task_logs_with_http_info(task_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_task_logs_with_http_info(task_id, **kwargs)  # noqa: E501
+            return data
+
+    def get_task_logs_with_http_info(self, task_id, **kwargs):  # noqa: E501
+        """Get Task Execution Logs  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_logs_with_http_info(task_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_id: (required)
+        :return: list[TaskExecLog]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["task_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_task_logs" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'task_id' is set
+        if "task_id" not in params or params["task_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_id` when calling `get_task_logs`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_id" in params:
+            path_params["taskId"] = params["task_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/{taskId}/log",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TaskExecLog]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def log(self, body, task_id, **kwargs):  # noqa: E501
+        """Log Task Execution Details  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.log(body, task_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str task_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.log_with_http_info(body, task_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.log_with_http_info(body, task_id, **kwargs)  # noqa: E501
+            return data
+
+    def log_with_http_info(self, body, task_id, **kwargs):  # noqa: E501
+        """Log Task Execution Details  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.log_with_http_info(body, task_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str body: (required)
+        :param str task_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "task_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method log" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `log`"
+            )  # noqa: E501
+        # verify the required parameter 'task_id' is set
+        if "task_id" not in params or params["task_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_id` when calling `log`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_id" in params:
+            path_params["taskId"] = params["task_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/{taskId}/log",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def poll(self, tasktype, **kwargs):  # noqa: E501
+        """Poll for a task of a certain type  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.poll(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :param str workerid:
+        :param str domain:
+        :return: Task
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.poll_with_http_info(tasktype, **kwargs)  # noqa: E501
+        else:
+            (data) = self.poll_with_http_info(tasktype, **kwargs)  # noqa: E501
+            return data
+
+    def poll_with_http_info(self, tasktype, **kwargs):  # noqa: E501
+        """Poll for a task of a certain type  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.poll_with_http_info(tasktype, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str tasktype: (required)
+        :param str workerid:
+        :param str domain:
+        :return: Task
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["tasktype", "workerid", "domain"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method poll" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'tasktype' is set
+        if "tasktype" not in params or params["tasktype"] is None:
+            raise ValueError(
+                "Missing the required parameter `tasktype` when calling `poll`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "tasktype" in params:
+            path_params["tasktype"] = params["tasktype"]  # noqa: E501
+
+        query_params = []
+        if "workerid" in params:
+            query_params.append(("workerid", params["workerid"]))  # noqa: E501
+        if "domain" in params:
+            query_params.append(("domain", params["domain"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        urllib3_logger = logging.getLogger("urllib3")
+        urllib3_logger.setLevel(logging.WARNING)
+        return self.api_client.call_api(
+            "/tasks/poll/{tasktype}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Task",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def requeue_pending_task(self, task_type, **kwargs):  # noqa: E501
+        """Requeue pending tasks  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.requeue_pending_task(task_type, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_type: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.requeue_pending_task_with_http_info(
+                task_type, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.requeue_pending_task_with_http_info(
+                task_type, **kwargs
+            )  # noqa: E501
+            return data
+
+    def requeue_pending_task_with_http_info(self, task_type, **kwargs):  # noqa: E501
+        """Requeue pending tasks  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.requeue_pending_task_with_http_info(task_type, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_type: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["task_type"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method requeue_pending_task" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'task_type' is set
+        if "task_type" not in params or params["task_type"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_type` when calling `requeue_pending_task`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_type" in params:
+            path_params["taskType"] = params["task_type"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["text/plain"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/queue/requeue/{taskType}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="str",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def search1(self, **kwargs):  # noqa: E501
+        """Search for tasks based in payload and other parameters  # noqa: E501
+
+        use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC. If order is not specified, defaults to ASC  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search1(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int start:
+        :param int size:
+        :param str sort:
+        :param str free_text:
+        :param str query:
+        :return: SearchResultTaskSummary
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.search1_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.search1_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def search1_with_http_info(self, **kwargs):  # noqa: E501
+        """Search for tasks based in payload and other parameters  # noqa: E501
+
+        use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC. If order is not specified, defaults to ASC  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search1_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int start:
+        :param int size:
+        :param str sort:
+        :param str free_text:
+        :param str query:
+        :return: SearchResultTaskSummary
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["start", "size", "sort", "free_text", "query"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method search1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "start" in params:
+            query_params.append(("start", params["start"]))  # noqa: E501
+        if "size" in params:
+            query_params.append(("size", params["size"]))  # noqa: E501
+        if "sort" in params:
+            query_params.append(("sort", params["sort"]))  # noqa: E501
+        if "free_text" in params:
+            query_params.append(("freeText", params["free_text"]))  # noqa: E501
+        if "query" in params:
+            query_params.append(("query", params["query"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/search",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="SearchResultTaskSummary",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def search_v21(self, **kwargs):  # noqa: E501
+        """Search for tasks based in payload and other parameters  # noqa: E501
+
+        use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC. If order is not specified, defaults to ASC  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search_v21(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int start:
+        :param int size:
+        :param str sort:
+        :param str free_text:
+        :param str query:
+        :return: SearchResultTask
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.search_v21_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.search_v21_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def search_v21_with_http_info(self, **kwargs):  # noqa: E501
+        """Search for tasks based in payload and other parameters  # noqa: E501
+
+        use sort options as sort=<field>:ASC|DESC e.g. sort=name&sort=workflowId:DESC. If order is not specified, defaults to ASC  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search_v21_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param int start:
+        :param int size:
+        :param str sort:
+        :param str free_text:
+        :param str query:
+        :return: SearchResultTask
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["start", "size", "sort", "free_text", "query"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method search_v21" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "start" in params:
+            query_params.append(("start", params["start"]))  # noqa: E501
+        if "size" in params:
+            query_params.append(("size", params["size"]))  # noqa: E501
+        if "sort" in params:
+            query_params.append(("sort", params["sort"]))  # noqa: E501
+        if "free_text" in params:
+            query_params.append(("freeText", params["free_text"]))  # noqa: E501
+        if "query" in params:
+            query_params.append(("query", params["query"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/search-v2",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="SearchResultTask",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def size(self, **kwargs):  # noqa: E501
+        """Get Task type queue sizes  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.size(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] task_type:
+        :return: dict(str, int)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.size_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.size_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def size_with_http_info(self, **kwargs):  # noqa: E501
+        """Get Task type queue sizes  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.size_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] task_type:
+        :return: dict(str, int)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["task_type"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method size" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "task_type" in params:
+            query_params.append(("taskType", params["task_type"]))  # noqa: E501
+            collection_formats["taskType"] = "multi"  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/queue/sizes",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, int)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_task(self, body, **kwargs):  # noqa: E501
+        """Update a task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TaskResult body: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_task_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.update_task_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def update_task_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Update a task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TaskResult body: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_task" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_task`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["text/plain"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="str",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_task1(
+        self, body, workflow_id, task_ref_name, status, **kwargs
+    ):  # noqa: E501
+        """Update a task By Ref Name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task1(body, workflow_id, task_ref_name, status, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :param str task_ref_name: (required)
+        :param str status: (required)
+        :param str workerid:
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_task1_with_http_info(
+                body, workflow_id, task_ref_name, status, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.update_task1_with_http_info(
+                body, workflow_id, task_ref_name, status, **kwargs
+            )  # noqa: E501
+            return data
+
+    def update_task1_with_http_info(
+        self, body, workflow_id, task_ref_name, status, **kwargs
+    ):  # noqa: E501
+        """Update a task By Ref Name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task1_with_http_info(body, workflow_id, task_ref_name, status, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :param str task_ref_name: (required)
+        :param str status: (required)
+        :param str workerid:
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "workflow_id", "task_ref_name", "status"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_task1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_task1`"
+            )  # noqa: E501
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `update_task1`"
+            )  # noqa: E501
+        # verify the required parameter 'task_ref_name' is set
+        if "task_ref_name" not in params or params["task_ref_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_ref_name` when calling `update_task1`"
+            )  # noqa: E501
+        # verify the required parameter 'status' is set
+        if "status" not in params or params["status"] is None:
+            raise ValueError(
+                "Missing the required parameter `status` when calling `update_task1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+        if "task_ref_name" in params:
+            path_params["taskRefName"] = params["task_ref_name"]  # noqa: E501
+        if "status" in params:
+            path_params["status"] = params["status"]  # noqa: E501
+
+        query_params = []
+
+        if "workerid" not in params:
+            params["workerid"] = socket.gethostname()
+        query_params.append(("workerid", params["workerid"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["text/plain"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/{workflowId}/{taskRefName}/{status}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="str",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_task_sync(
+        self, body, workflow_id, task_ref_name, status, **kwargs
+    ):  # noqa: E501
+        """Update a task By Ref Name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task_sync(body, workflow_id, task_ref_name, status, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :param str task_ref_name: (required)
+        :param str status: (required)
+        :param str workerid:
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_task_sync_with_http_info(
+                body, workflow_id, task_ref_name, status, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.update_task_sync_with_http_info(
+                body, workflow_id, task_ref_name, status, **kwargs
+            )  # noqa: E501
+            return data
+
+    def update_task_sync_with_http_info(
+        self, body, workflow_id, task_ref_name, status, **kwargs
+    ):  # noqa: E501
+        """Update a task By Ref Name  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_task_sync_with_http_info(body, workflow_id, task_ref_name, status, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :param str task_ref_name: (required)
+        :param str status: (required)
+        :param str workerid:
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "workflow_id", "task_ref_name", "status"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_task1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_task1`"
+            )  # noqa: E501
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `update_task1`"
+            )  # noqa: E501
+        # verify the required parameter 'task_ref_name' is set
+        if "task_ref_name" not in params or params["task_ref_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_ref_name` when calling `update_task1`"
+            )  # noqa: E501
+        # verify the required parameter 'status' is set
+        if "status" not in params or params["status"] is None:
+            raise ValueError(
+                "Missing the required parameter `status` when calling `update_task1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+        if "task_ref_name" in params:
+            path_params["taskRefName"] = params["task_ref_name"]  # noqa: E501
+        if "status" in params:
+            path_params["status"] = params["status"]  # noqa: E501
+
+        query_params = []
+
+        if "workerid" not in params:
+            params["workerid"] = socket.gethostname()
+        query_params.append(("workerid", params["workerid"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["text/plain"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/tasks/{workflowId}/{taskRefName}/{status}/sync",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Workflow",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/token_resource_api.py b/omagent_core/engine/http/api/token_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..44fcd1a34977c54b81e8697b9fcedef405ec033b
--- /dev/null
+++ b/omagent_core/engine/http/api/token_resource_api.py
@@ -0,0 +1,212 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class TokenResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def generate_token(self, body, **kwargs):  # noqa: E501
+        """Generate JWT with the given access key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.generate_token(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param GenerateTokenRequest body: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.generate_token_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.generate_token_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def generate_token_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Generate JWT with the given access key  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.generate_token_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param GenerateTokenRequest body: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method generate_token" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `generate_token`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/token",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Response",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_user_info(self, **kwargs):  # noqa: E501
+        """Get the user info from the token  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_user_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_user_info_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_user_info_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_user_info_with_http_info(self, **kwargs):  # noqa: E501
+        """Get the user info from the token  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_user_info_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_user_info" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/token/userInfo",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/user_resource_api.py b/omagent_core/engine/http/api/user_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..89416549e80eda4cc82938fda828ff2aaa1c5246
--- /dev/null
+++ b/omagent_core/engine/http/api/user_resource_api.py
@@ -0,0 +1,520 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class UserResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def delete_user(self, id, **kwargs):  # noqa: E501
+        """Delete a user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_user(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_user_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.delete_user_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def delete_user_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Delete a user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_user_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: Response
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_user" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `delete_user`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/users/{id}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Response",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_granted_permissions(self, user_id, **kwargs):  # noqa: E501
+        """Get the permissions this user has over workflows and tasks  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_granted_permissions(user_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str user_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_granted_permissions_with_http_info(
+                user_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_granted_permissions_with_http_info(
+                user_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_granted_permissions_with_http_info(self, user_id, **kwargs):  # noqa: E501
+        """Get the permissions this user has over workflows and tasks  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_granted_permissions_with_http_info(user_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str user_id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["user_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_granted_permissions" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'user_id' is set
+        if "user_id" not in params or params["user_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `user_id` when calling `get_granted_permissions`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "user_id" in params:
+            path_params["userId"] = params["user_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/users/{userId}/permissions",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_user(self, id, **kwargs):  # noqa: E501
+        """Get a user by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_user(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_user_with_http_info(id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_user_with_http_info(id, **kwargs)  # noqa: E501
+            return data
+
+    def get_user_with_http_info(self, id, **kwargs):  # noqa: E501
+        """Get a user by id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_user_with_http_info(id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_user" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `get_user`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/users/{id}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def list_users(self, **kwargs):  # noqa: E501
+        """Get all users  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_users(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param bool apps:
+        :return: list[ConductorUser]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.list_users_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.list_users_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def list_users_with_http_info(self, **kwargs):  # noqa: E501
+        """Get all users  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.list_users_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param bool apps:
+        :return: list[ConductorUser]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["apps"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method list_users" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "apps" in params:
+            query_params.append(("apps", params["apps"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/users",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[ConductorUser]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def upsert_user(self, body, id, **kwargs):  # noqa: E501
+        """Create or update a user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.upsert_user(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param UpsertUserRequest body: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.upsert_user_with_http_info(body, id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.upsert_user_with_http_info(body, id, **kwargs)  # noqa: E501
+            return data
+
+    def upsert_user_with_http_info(self, body, id, **kwargs):  # noqa: E501
+        """Create or update a user  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.upsert_user_with_http_info(body, id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param UpsertUserRequest body: (required)
+        :param str id: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method upsert_user" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `upsert_user`"
+            )  # noqa: E501
+        # verify the required parameter 'id' is set
+        if "id" not in params or params["id"] is None:
+            raise ValueError(
+                "Missing the required parameter `id` when calling `upsert_user`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "id" in params:
+            path_params["id"] = params["id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/users/{id}",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/workflow_bulk_resource_api.py b/omagent_core/engine/http/api/workflow_bulk_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..60cdd4b0773dc0dacff62db67224efe55d474c1e
--- /dev/null
+++ b/omagent_core/engine/http/api/workflow_bulk_resource_api.py
@@ -0,0 +1,555 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class WorkflowBulkResourceApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def pause_workflow(self, body, **kwargs):  # noqa: E501
+        """Pause the list of workflows  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_workflow(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.pause_workflow_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.pause_workflow_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def pause_workflow_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Pause the list of workflows  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_workflow_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method pause_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `pause_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/bulk/pause",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="BulkResponse",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def restart(self, body, **kwargs):  # noqa: E501
+        """Restart the list of completed workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.restart(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :param bool use_latest_definitions:
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.restart_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.restart_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def restart_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Restart the list of completed workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.restart_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :param bool use_latest_definitions:
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "use_latest_definitions"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method restart" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `restart`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "use_latest_definitions" in params:
+            query_params.append(
+                ("useLatestDefinitions", params["use_latest_definitions"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/bulk/restart",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="BulkResponse",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def resume_workflow(self, body, **kwargs):  # noqa: E501
+        """Resume the list of workflows  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_workflow(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.resume_workflow_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.resume_workflow_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def resume_workflow_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Resume the list of workflows  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_workflow_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method resume_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `resume_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/bulk/resume",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="BulkResponse",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def retry(self, body, **kwargs):  # noqa: E501
+        """Retry the last failed task for each workflow from the list  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.retry(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.retry_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.retry_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def retry_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Retry the last failed task for each workflow from the list  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.retry_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method retry" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `retry`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/bulk/retry",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="BulkResponse",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def terminate(self, body, **kwargs):  # noqa: E501
+        """Terminate workflows execution  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.terminate(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :param str reason:
+        :param bool trigger_failure_workflow:
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.terminate_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.terminate_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def terminate_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Terminate workflows execution  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.terminate_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :param str reason:
+        :param bool trigger_failure_workflow:
+        :return: BulkResponse
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "reason", "triggerFailureWorkflow"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method terminate" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `terminate`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "reason" in params:
+            query_params.append(("reason", params["reason"]))  # noqa: E501
+
+        if "triggerFailureWorkflow" in params:
+            query_params.append(
+                ("triggerFailureWorkflow", params["triggerFailureWorkflow"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = []  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/bulk/terminate",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="BulkResponse",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api/workflow_resource_api.py b/omagent_core/engine/http/api/workflow_resource_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..c120782c405185bd60ce22289e5bc466041e4f51
--- /dev/null
+++ b/omagent_core/engine/http/api/workflow_resource_api.py
@@ -0,0 +1,3372 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+import uuid
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class WorkflowResourceApi(object):
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def decide(self, workflow_id, **kwargs):  # noqa: E501
+        """Starts the decision task for a workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.decide(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.decide_with_http_info(workflow_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.decide_with_http_info(workflow_id, **kwargs)  # noqa: E501
+            return data
+
+    def decide_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Starts the decision task for a workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.decide_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method decide" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `decide`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/decide/{workflowId}",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete(self, workflow_id, **kwargs):  # noqa: E501
+        """Removes the workflow from the system  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool archive_workflow:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete1_with_http_info(workflow_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.delete1_with_http_info(workflow_id, **kwargs)  # noqa: E501
+            return data
+
+    def delete1_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Removes the workflow from the system  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete1_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool archive_workflow:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id", "archive_workflow"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method delete1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `delete1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "archive_workflow" in params:
+            query_params.append(
+                ("archiveWorkflow", params["archive_workflow"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/remove",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def execute_workflow(self, body, request_id, name, version, **kwargs):  # noqa: E501
+        if request_id is None:
+            request_id = str(uuid.uuid4())
+        """Execute a workflow synchronously  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.execute_workflow(body, request_id, name, version, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param StartWorkflowRequest body: (required)
+        :param str request_id: (required)
+        :param str name: (required)
+        :param int version: (required)
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :return: WorkflowRun
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.execute_workflow_with_http_info(
+                body, request_id, name, version, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.execute_workflow_with_http_info(
+                body, request_id, name, version, **kwargs
+            )  # noqa: E501
+            return data
+
+    def execute_workflow_with_http_info(
+        self, body, request_id, name, version, **kwargs
+    ):  # noqa: E501
+        """Execute a workflow synchronously  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.execute_workflow_with_http_info(body, request_id, name, version, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param StartWorkflowRequest body: (required)
+        :param str request_id: (required)
+        :param str name: (required)
+        :param int version: (required)
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :return: WorkflowRun
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "body",
+            "request_id",
+            "name",
+            "version",
+            "wait_until_task_ref",
+            "wait_for_seconds",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method execute_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `execute_workflow`"
+            )  # noqa: E501
+        # verify the required parameter 'request_id' is set
+        if "request_id" not in params or params["request_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `request_id` when calling `execute_workflow`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `execute_workflow`"
+            )  # noqa: E501
+        # verify the required parameter 'version' is set
+        if "version" not in params or params["version"] is None:
+            raise ValueError(
+                "Missing the required parameter `version` when calling `execute_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "version" in params:
+            path_params["version"] = params["version"]  # noqa: E501
+
+        query_params = []
+        if "request_id" in params:
+            query_params.append(("requestId", params["request_id"]))  # noqa: E501
+        if "wait_until_task_ref" in params:
+            query_params.append(
+                ("waitUntilTaskRef", params["wait_until_task_ref"])
+            )  # noqa: E501
+        if "wait_for_seconds" in params:
+            query_params.append(
+                ("waitForSeconds", params["wait_for_seconds"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/execute/{name}/{version}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="WorkflowRun",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def execute_workflow_as_api(self, body, name, **kwargs):  # noqa: E501
+        """Execute a workflow synchronously with input and outputs  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.execute_workflow_as_api(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str name: (required)
+        :param str request_id:
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :param str authorization:
+        :param int version:
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.execute_workflow_as_api_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.execute_workflow_as_api_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def execute_workflow_as_api_with_http_info(
+        self, body, name, **kwargs
+    ):  # noqa: E501
+        """Execute a workflow synchronously with input and outputs  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.execute_workflow_as_api_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str name: (required)
+        :param str request_id:
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :param str authorization:
+        :param int version:
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "body",
+            "name",
+            "request_id",
+            "wait_until_task_ref",
+            "wait_for_seconds",
+            "authorization",
+            "version",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method execute_workflow_as_api" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `execute_workflow_as_api`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `execute_workflow_as_api`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+
+        header_params = {}
+        if "request_id" in params:
+            header_params["requestId"] = params["request_id"]  # noqa: E501
+        if "wait_until_task_ref" in params:
+            header_params["waitUntilTaskRef"] = params[
+                "wait_until_task_ref"
+            ]  # noqa: E501
+        if "wait_for_seconds" in params:
+            header_params["waitForSeconds"] = params["wait_for_seconds"]  # noqa: E501
+        if "authorization" in params:
+            header_params["authorization"] = params["authorization"]  # noqa: E501
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/execute/{name}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, object)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def execute_workflow_as_get_api(self, name, **kwargs):  # noqa: E501
+        """Execute a workflow synchronously with input and outputs using get api  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.execute_workflow_as_get_api(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :param str request_id:
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :param str authorization:
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.execute_workflow_as_get_api_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.execute_workflow_as_get_api_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def execute_workflow_as_get_api_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Execute a workflow synchronously with input and outputs using get api  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.execute_workflow_as_get_api_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :param str request_id:
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :param str authorization:
+        :return: dict(str, object)
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "name",
+            "version",
+            "request_id",
+            "wait_until_task_ref",
+            "wait_for_seconds",
+            "authorization",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method execute_workflow_as_get_api" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `execute_workflow_as_get_api`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+
+        header_params = {}
+        if "request_id" in params:
+            header_params["requestId"] = params["request_id"]  # noqa: E501
+        if "wait_until_task_ref" in params:
+            header_params["waitUntilTaskRef"] = params[
+                "wait_until_task_ref"
+            ]  # noqa: E501
+        if "wait_for_seconds" in params:
+            header_params["waitForSeconds"] = params["wait_for_seconds"]  # noqa: E501
+        if "authorization" in params:
+            header_params["authorization"] = params["authorization"]  # noqa: E501
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/execute/{name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, object)",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_execution_status(self, workflow_id, **kwargs):  # noqa: E501
+        """Gets the workflow by workflow id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_execution_status(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool include_tasks:
+        :param bool summarize:
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_execution_status_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_execution_status_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_execution_status_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Gets the workflow by workflow id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_execution_status_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool include_tasks:
+        :param bool summarize:
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id", "include_tasks", "summarize"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_execution_status" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `get_execution_status`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "include_tasks" in params:
+            query_params.append(("includeTasks", params["include_tasks"]))  # noqa: E501
+        if "summarize" in params:
+            query_params.append(("summarize", params["summarize"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Workflow",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_execution_status_task_list(self, workflow_id, **kwargs):  # noqa: E501
+        """Gets the workflow tasks by workflow id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_execution_status_task_list(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param int start:
+        :param int count:
+        :param list[str] status:
+        :return: TaskListSearchResultSummary
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_execution_status_task_list_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_execution_status_task_list_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_execution_status_task_list_with_http_info(
+        self, workflow_id, **kwargs
+    ):  # noqa: E501
+        """Gets the workflow tasks by workflow id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_execution_status_task_list_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param int start:
+        :param int count:
+        :param list[str] status:
+        :return: TaskListSearchResultSummary
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id", "start", "count", "status"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_execution_status_task_list" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `get_execution_status_task_list`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "start" in params:
+            query_params.append(("start", params["start"]))  # noqa: E501
+        if "count" in params:
+            query_params.append(("count", params["count"]))  # noqa: E501
+        if "status" in params:
+            query_params.append(("status", params["status"]))  # noqa: E501
+            collection_formats["status"] = "multi"  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/tasks",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="TaskListSearchResultSummary",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_running_workflow(self, name, **kwargs):  # noqa: E501
+        """Retrieve all the running workflows  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_running_workflow(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :param int start_time:
+        :param int end_time:
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_running_workflow_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_running_workflow_with_http_info(
+                name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_running_workflow_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Retrieve all the running workflows  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_running_workflow_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param int version:
+        :param int start_time:
+        :param int end_time:
+        :return: list[str]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name", "version", "start_time", "end_time"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_running_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_running_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+        if "start_time" in params:
+            query_params.append(("startTime", params["start_time"]))  # noqa: E501
+        if "end_time" in params:
+            query_params.append(("endTime", params["end_time"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/running/{name}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[str]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_workflow_status_summary(self, workflow_id, **kwargs):  # noqa: E501
+        """Gets the workflow by workflow id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflow_status_summary(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool include_output:
+        :param bool include_variables:
+        :return: WorkflowStatus
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_workflow_status_summary_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_workflow_status_summary_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_workflow_status_summary_with_http_info(
+        self, workflow_id, **kwargs
+    ):  # noqa: E501
+        """Gets the workflow by workflow id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflow_status_summary_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool include_output:
+        :param bool include_variables:
+        :return: WorkflowStatus
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "workflow_id",
+            "include_output",
+            "include_variables",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_workflow_status_summary" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `get_workflow_status_summary`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "include_output" in params:
+            query_params.append(
+                ("includeOutput", params["include_output"])
+            )  # noqa: E501
+        if "include_variables" in params:
+            query_params.append(
+                ("includeVariables", params["include_variables"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="WorkflowStatus",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_workflows(self, body, name, **kwargs):  # noqa: E501
+        """Lists workflows for the given correlation id list  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflows(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :param str name: (required)
+        :param bool include_closed:
+        :param bool include_tasks:
+        :return: dict(str, list[Workflow])
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_workflows_with_http_info(body, name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_workflows_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_workflows_with_http_info(self, body, name, **kwargs):  # noqa: E501
+        """Lists workflows for the given correlation id list  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflows_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[str] body: (required)
+        :param str name: (required)
+        :param bool include_closed:
+        :param bool include_tasks:
+        :return: dict(str, list[Workflow])
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name", "include_closed", "include_tasks"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_workflows" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `get_workflows`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_workflows`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "include_closed" in params:
+            query_params.append(
+                ("includeClosed", params["include_closed"])
+            )  # noqa: E501
+        if "include_tasks" in params:
+            query_params.append(("includeTasks", params["include_tasks"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{name}/correlated",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, list[Workflow])",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_workflows_by_correlation_id_in_batch(self, body, **kwargs):  # noqa: E501
+        """Lists workflows for the given correlation id list and workflow name list  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflows_by_correlation_id_in_batch(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param CorrelationIdsSearchRequest body: (required)
+        :param bool include_closed:
+        :param bool include_tasks:
+        :return: dict(str, list[Workflow])
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_workflows1_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_workflows1_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def get_workflows_batch(self, body, **kwargs):  # noqa: E501
+        """
+        deprecated:: Please use get_workflows_by_correlation_id_in_batch
+        Lists workflows for the given correlation id list and workflow name list  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflows_by_correlation_id_in_batch(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param CorrelationIdsSearchRequest body: (required)
+        :param bool include_closed:
+        :param bool include_tasks:
+        :return: dict(str, list[Workflow])
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_workflows1_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_workflows1_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def get_workflows1_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Lists workflows for the given correlation id list and workflow name list  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflows1_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param CorrelationIdsSearchRequest body: (required)
+        :param bool include_closed:
+        :param bool include_tasks:
+        :return: dict(str, list[Workflow])
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "include_closed", "include_tasks"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_workflows1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `get_workflows1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "include_closed" in params:
+            query_params.append(
+                ("includeClosed", params["include_closed"])
+            )  # noqa: E501
+        if "include_tasks" in params:
+            query_params.append(("includeTasks", params["include_tasks"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/correlated/batch",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="dict(str, list[Workflow])",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_workflows2(self, name, correlation_id, **kwargs):  # noqa: E501
+        """Lists workflows for the given correlation id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflows2(name, correlation_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str correlation_id: (required)
+        :param bool include_closed:
+        :param bool include_tasks:
+        :return: list[Workflow]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_workflows2_with_http_info(
+                name, correlation_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.get_workflows2_with_http_info(
+                name, correlation_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_workflows2_with_http_info(
+        self, name, correlation_id, **kwargs
+    ):  # noqa: E501
+        """Lists workflows for the given correlation id  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflows2_with_http_info(name, correlation_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :param str correlation_id: (required)
+        :param bool include_closed:
+        :param bool include_tasks:
+        :return: list[Workflow]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "name",
+            "correlation_id",
+            "include_closed",
+            "include_tasks",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_workflows2" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_workflows2`"
+            )  # noqa: E501
+        # verify the required parameter 'correlation_id' is set
+        if "correlation_id" not in params or params["correlation_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `correlation_id` when calling `get_workflows2`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+        if "correlation_id" in params:
+            path_params["correlationId"] = params["correlation_id"]  # noqa: E501
+
+        query_params = []
+        if "include_closed" in params:
+            query_params.append(
+                ("includeClosed", params["include_closed"])
+            )  # noqa: E501
+        if "include_tasks" in params:
+            query_params.append(("includeTasks", params["include_tasks"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{name}/correlated/{correlationId}",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[Workflow]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def jump_to_task(self, body, workflow_id, **kwargs):  # noqa: E501
+        """Jump workflow execution to given task  # noqa: E501
+
+        Jump workflow execution to given task.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.jump_to_task(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :param str task_reference_name:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.jump_to_task_with_http_info(
+                body, workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.jump_to_task_with_http_info(
+                body, workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def jump_to_task_with_http_info(self, body, workflow_id, **kwargs):  # noqa: E501
+        """Jump workflow execution to given task  # noqa: E501
+
+        Jump workflow execution to given task.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.jump_to_task_with_http_info(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :param str task_reference_name:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "workflow_id", "task_reference_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method jump_to_task" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `jump_to_task`"
+            )  # noqa: E501
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `jump_to_task`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "task_reference_name" in params:
+            query_params.append(
+                ("taskReferenceName", params["task_reference_name"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/jump/{taskReferenceName}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def pause_workflow1(self, workflow_id, **kwargs):  # noqa: E501
+        """
+        deprecated:: Please use pause_workflow(workflow_id) method
+        Parameters
+        ----------
+        workflow_id
+        kwargs
+
+        Returns
+        -------
+
+        """
+        self.pause_workflow(workflow_id)
+
+    def pause_workflow(self, workflow_id, **kwargs):  # noqa: E501
+        """Pauses the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_workflow(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.pause_workflow_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.pause_workflow_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def pause_workflow_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Pauses the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.pause_workflow_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method pause_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `pause_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/pause",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def rerun(self, body, workflow_id, **kwargs):  # noqa: E501
+        """Reruns the workflow from a specific task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.rerun(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param RerunWorkflowRequest body: (required)
+        :param str workflow_id: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.rerun_with_http_info(body, workflow_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.rerun_with_http_info(
+                body, workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def rerun_with_http_info(self, body, workflow_id, **kwargs):  # noqa: E501
+        """Reruns the workflow from a specific task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.rerun_with_http_info(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param RerunWorkflowRequest body: (required)
+        :param str workflow_id: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "workflow_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method rerun" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `rerun`"
+            )  # noqa: E501
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `rerun`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["text/plain"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/rerun",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="str",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def reset_workflow(self, workflow_id, **kwargs):  # noqa: E501
+        """Resets callback times of all non-terminal SIMPLE tasks to 0  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.reset_workflow(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.reset_workflow_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.reset_workflow_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def reset_workflow_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Resets callback times of all non-terminal SIMPLE tasks to 0  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.reset_workflow_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method reset_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `reset_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/resetcallbacks",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def restart1(self, workflow_id, **kwargs):  # noqa: E501
+        """
+        deprecated:: Please use restart(workflow_id) method
+        Parameters
+        ----------
+        workflow_id
+        kwargs
+
+        Returns
+        -------
+
+        """
+        return self.restart(workflow_id)
+
+    def restart(self, workflow_id, **kwargs):  # noqa: E501
+        """Restarts a completed workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.restart(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool use_latest_definitions:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.restart_with_http_info(workflow_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.restart_with_http_info(workflow_id, **kwargs)  # noqa: E501
+            return data
+
+    def restart_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Restarts a completed workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.restart_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool use_latest_definitions:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id", "use_latest_definitions"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method restart" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `restart`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "use_latest_definitions" in params:
+            query_params.append(
+                ("useLatestDefinitions", params["use_latest_definitions"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/restart",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def resume_workflow1(self, workflow_id):  # noqa: E501
+        """
+        deprecated:: Please use resume_workflow(workflow_id) method
+        Parameters
+        ----------
+        workflow_id
+
+        Returns
+        -------
+
+        """
+        return self.resume_workflow(workflow_id)
+
+    def resume_workflow(self, workflow_id, **kwargs):  # noqa: E501
+        """Resumes the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_workflow(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.resume_workflow_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.resume_workflow_with_http_info(
+                workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def resume_workflow_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Resumes the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.resume_workflow_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method resume_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `resume_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/resume",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def retry1(self, workflow_id, **kwargs):  # noqa: E501
+        """
+        deprecated:: Please use retry(workflow_id) method
+        Parameters
+        ----------
+        workflow_id
+        kwargs
+
+        Returns
+        -------
+
+        """
+        return self.retry(workflow_id)
+
+    def retry(self, workflow_id, **kwargs):  # noqa: E501
+        """Retries the last failed task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.retry(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool resume_subworkflow_tasks:
+        :param bool retry_if_retried_by_parent:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.retry_with_http_info(workflow_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.retry_with_http_info(workflow_id, **kwargs)  # noqa: E501
+            return data
+
+    def retry_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Retries the last failed task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.retry_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param bool resume_subworkflow_tasks:
+        :param bool retry_if_retried_by_parent:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "workflow_id",
+            "resume_subworkflow_tasks",
+            "retry_if_retried_by_parent",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method retry" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `retry`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "resume_subworkflow_tasks" in params:
+            query_params.append(
+                ("resumeSubworkflowTasks", params["resume_subworkflow_tasks"])
+            )  # noqa: E501
+        if "retry_if_retried_by_parent" in params:
+            query_params.append(
+                ("retryIfRetriedByParent", params["retry_if_retried_by_parent"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/retry",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def search(self, **kwargs):  # noqa: E501
+        """Search for workflows based on payload and other parameters  # noqa: E501
+
+        Search for workflows based on payload and other parameters. The query parameter accepts exact matches using `=` and `IN` on the following fields: `workflowId`, `correlationId`, `taskId`, `workflowType`, `taskType`, and `status`.  Matches using `=` can be written as `taskType = HTTP`.  Matches using `IN` are written as `status IN (SCHEDULED, IN_PROGRESS)`. The 'startTime' and 'modifiedTime' field uses unix timestamps and accepts queries using `<` and `>`, for example `startTime < 1696143600000`. Queries can be combined using `AND`, for example `taskType = HTTP AND status = SCHEDULED`.   # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str query_id:
+        :param int start:
+        :param int size:
+        :param str free_text:
+        :param str query:
+        :param bool skip_cache:
+        :return: ScrollableSearchResultWorkflowSummary
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.search_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.search_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def search_with_http_info(self, **kwargs):  # noqa: E501
+        """Search for workflows based on payload and other parameters  # noqa: E501
+
+        Search for workflows based on payload and other parameters. The query parameter accepts exact matches using `=` and `IN` on the following fields: `workflowId`, `correlationId`, `taskId`, `workflowType`, `taskType`, and `status`.  Matches using `=` can be written as `taskType = HTTP`.  Matches using `IN` are written as `status IN (SCHEDULED, IN_PROGRESS)`. The 'startTime' and 'modifiedTime' field uses unix timestamps and accepts queries using `<` and `>`, for example `startTime < 1696143600000`. Queries can be combined using `AND`, for example `taskType = HTTP AND status = SCHEDULED`.   # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.search_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str query_id:
+        :param int start:
+        :param int size:
+        :param str free_text:
+        :param str query:
+        :param bool skip_cache:
+        :return: ScrollableSearchResultWorkflowSummary
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "query_id",
+            "start",
+            "size",
+            "free_text",
+            "query",
+            "skip_cache",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'" " to method search" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+        if "query_id" in params:
+            query_params.append(("queryId", params["query_id"]))  # noqa: E501
+        if "start" in params:
+            query_params.append(("start", params["start"]))  # noqa: E501
+        if "size" in params:
+            query_params.append(("size", params["size"]))  # noqa: E501
+        if "free_text" in params:
+            query_params.append(("freeText", params["free_text"]))  # noqa: E501
+        if "query" in params:
+            query_params.append(("query", params["query"]))  # noqa: E501
+        if "skip_cache" in params:
+            query_params.append(("skipCache", params["skip_cache"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/search",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="ScrollableSearchResultWorkflowSummary",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def skip_task_from_workflow(
+        self, workflow_id, task_reference_name, skip_task_request, **kwargs
+    ):  # noqa: E501
+        """Skips a given task from a current running workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.skip_task_from_workflow(workflow_id, task_reference_name, skip_task_request, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param str task_reference_name: (required)
+        :param SkipTaskRequest skip_task_request: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.skip_task_from_workflow_with_http_info(
+                workflow_id, task_reference_name, skip_task_request, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.skip_task_from_workflow_with_http_info(
+                workflow_id, task_reference_name, skip_task_request, **kwargs
+            )  # noqa: E501
+            return data
+
+    def skip_task_from_workflow_with_http_info(
+        self, workflow_id, task_reference_name, skip_task_request, **kwargs
+    ):  # noqa: E501
+        """Skips a given task from a current running workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.skip_task_from_workflow_with_http_info(workflow_id, task_reference_name, skip_task_request, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param str task_reference_name: (required)
+        :param SkipTaskRequest skip_task_request: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "workflow_id",
+            "task_reference_name",
+            "skip_task_request",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method skip_task_from_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `skip_task_from_workflow`"
+            )  # noqa: E501
+        # verify the required parameter 'task_reference_name' is set
+        if "task_reference_name" not in params or params["task_reference_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_reference_name` when calling `skip_task_from_workflow`"
+            )  # noqa: E501
+        # verify the required parameter 'skip_task_request' is set
+        if "skip_task_request" not in params or params["skip_task_request"] is None:
+            raise ValueError(
+                "Missing the required parameter `skip_task_request` when calling `skip_task_from_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+        if "task_reference_name" in params:
+            path_params["taskReferenceName"] = params[
+                "task_reference_name"
+            ]  # noqa: E501
+
+        query_params = []
+        if "skip_task_request" in params:
+            query_params.append(
+                ("skipTaskRequest", params["skip_task_request"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/skiptask/{taskReferenceName}",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def start_workflow(self, body, **kwargs):  # noqa: E501
+        """Start a new workflow with StartWorkflowRequest, which allows task to be executed in a domain  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.start_workflow(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param StartWorkflowRequest body: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.start_workflow_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.start_workflow_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def start_workflow_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Start a new workflow with StartWorkflowRequest, which allows task to be executed in a domain  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.start_workflow_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param StartWorkflowRequest body: (required)
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method start_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `start_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["text/plain"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="str",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def start_workflow1(self, body, name, **kwargs):  # noqa: E501
+        """Start a new workflow. Returns the ID of the workflow instance that can be later used for tracking  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.start_workflow1(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str name: (required)
+        :param int version:
+        :param str correlation_id:
+        :param int priority:
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.start_workflow1_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.start_workflow1_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def start_workflow1_with_http_info(self, body, name, **kwargs):  # noqa: E501
+        """Start a new workflow. Returns the ID of the workflow instance that can be later used for tracking  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.start_workflow1_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str name: (required)
+        :param int version:
+        :param str correlation_id:
+        :param int priority:
+        :return: str
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "body",
+            "name",
+            "version",
+            "correlation_id",
+            "priority",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method start_workflow1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `start_workflow1`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `start_workflow1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+        if "version" in params:
+            query_params.append(("version", params["version"]))  # noqa: E501
+        if "correlation_id" in params:
+            query_params.append(
+                ("correlationId", params["correlation_id"])
+            )  # noqa: E501
+        if "priority" in params:
+            query_params.append(("priority", params["priority"]))  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["text/plain"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{name}",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="str",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def terminate1(self, workflow_id, **kwargs):  # noqa: E501
+        """
+        deprecated:: Please use terminate(workflow_id) method
+        Parameters
+        ----------
+        workflow_id
+        kwargs
+
+        Returns
+        -------
+
+        """
+        options = {}
+        if "triggerFailureWorkflow" in kwargs.keys():
+            options["trigger_failure_workflow"] = kwargs["triggerFailureWorkflow"]
+
+        return self.terminate(workflow_id, **options)
+
+    def terminate(self, workflow_id, **kwargs):  # noqa: E501
+        """Terminate workflow execution  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.terminate1(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param str reason:
+        :param bool trigger_failure_workflow:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if workflow_id is None:
+            raise Exception("Missing workflow id")
+        if kwargs.get("async_req"):
+            return self.terminate1_with_http_info(workflow_id, **kwargs)  # noqa: E501
+        else:
+            (data) = self.terminate1_with_http_info(workflow_id, **kwargs)  # noqa: E501
+            return data
+
+    def terminate1_with_http_info(self, workflow_id, **kwargs):  # noqa: E501
+        """Terminate workflow execution  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.terminate1_with_http_info(workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str workflow_id: (required)
+        :param str reason:
+        :param bool trigger_failure_workflow:
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["workflow_id", "reason", "trigger_failure_workflow"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method terminate1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `terminate1`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "reason" in params:
+            query_params.append(("reason", params["reason"]))  # noqa: E501
+        if "trigger_failure_workflow" in params:
+            query_params.append(
+                ("triggerFailureWorkflow", params["trigger_failure_workflow"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def test_workflow(self, body, **kwargs):  # noqa: E501
+        """Test workflow execution using mock data  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.test_workflow(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowTestRequest body: (required)
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.test_workflow_with_http_info(body, **kwargs)  # noqa: E501
+        else:
+            (data) = self.test_workflow_with_http_info(body, **kwargs)  # noqa: E501
+            return data
+
+    def test_workflow_with_http_info(self, body, **kwargs):  # noqa: E501
+        """Test workflow execution using mock data  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.test_workflow_with_http_info(body, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowTestRequest body: (required)
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method test_workflow" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `test_workflow`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/test",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Workflow",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_workflow_state(self, body, workflow_id, **kwargs):  # noqa: E501
+        """Update workflow variables  # noqa: E501
+
+        Updates the workflow variables and triggers evaluation.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_workflow_state(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_workflow_state_with_http_info(
+                body, workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.update_workflow_state_with_http_info(
+                body, workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def update_workflow_state_with_http_info(
+        self, body, workflow_id, **kwargs
+    ):  # noqa: E501
+        """Update workflow variables  # noqa: E501
+
+        Updates the workflow variables and triggers evaluation.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_workflow_state_with_http_info(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param dict(str, object) body: (required)
+        :param str workflow_id: (required)
+        :return: Workflow
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "workflow_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_workflow_state" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_workflow_state`"
+            )  # noqa: E501
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `update_workflow_state`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/variables",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="Workflow",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def upgrade_running_workflow_to_version(
+        self, body, workflow_id, **kwargs
+    ):  # noqa: E501
+        """Upgrade running workflow to newer version  # noqa: E501
+
+        Upgrade running workflow to newer version  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.upgrade_running_workflow_to_version(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param UpgradeWorkflowRequest body: (required)
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.upgrade_running_workflow_to_version_with_http_info(
+                body, workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.upgrade_running_workflow_to_version_with_http_info(
+                body, workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def upgrade_running_workflow_to_version_with_http_info(
+        self, body, workflow_id, **kwargs
+    ):  # noqa: E501
+        """Upgrade running workflow to newer version  # noqa: E501
+
+        Upgrade running workflow to newer version  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.upgrade_running_workflow_to_version_with_http_info(body, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param UpgradeWorkflowRequest body: (required)
+        :param str workflow_id: (required)
+        :return: None
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "workflow_id"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method upgrade_running_workflow_to_version" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `upgrade_running_workflow_to_version`"
+            )  # noqa: E501
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `upgrade_running_workflow_to_version`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/upgrade",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type=None,  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def update_workflow_and_task_state(
+        self, update_requesst, workflow_id, **kwargs
+    ):  # noqa: E501
+        request_id = str(uuid.uuid4())
+        """Update a workflow state by updating variables or in progress task  # noqa: E501
+
+        Updates the workflow variables, tasks and triggers evaluation.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_workflow_and_task_state(update_requesst, request_id, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowStateUpdate body: (required)
+        :param str request_id: (required)
+        :param str workflow_id: (required)
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :return: WorkflowRun
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.update_workflow_and_task_state_with_http_info(
+                update_requesst, request_id, workflow_id, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.update_workflow_and_task_state_with_http_info(
+                update_requesst, request_id, workflow_id, **kwargs
+            )  # noqa: E501
+            return data
+
+    def update_workflow_and_task_state_with_http_info(
+        self, body, request_id, workflow_id, **kwargs
+    ):  # noqa: E501
+        """Update a workflow state by updating variables or in progress task  # noqa: E501
+
+        Updates the workflow variables, tasks and triggers evaluation.  # noqa: E501
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.update_workflow_and_task_state_with_http_info(body, request_id, workflow_id, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param WorkflowStateUpdate body: (required)
+        :param str request_id: (required)
+        :param str workflow_id: (required)
+        :param str wait_until_task_ref:
+        :param int wait_for_seconds:
+        :return: WorkflowRun
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = [
+            "body",
+            "request_id",
+            "workflow_id",
+            "wait_until_task_ref",
+            "wait_for_seconds",
+        ]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method update_workflow_and_task_state" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `update_workflow_and_task_state`"
+            )  # noqa: E501
+        # verify the required parameter 'request_id' is set
+        if "request_id" not in params or params["request_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `request_id` when calling `update_workflow_and_task_state`"
+            )  # noqa: E501
+        # verify the required parameter 'workflow_id' is set
+        if "workflow_id" not in params or params["workflow_id"] is None:
+            raise ValueError(
+                "Missing the required parameter `workflow_id` when calling `update_workflow_and_task_state`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "workflow_id" in params:
+            path_params["workflowId"] = params["workflow_id"]  # noqa: E501
+
+        query_params = []
+        if "request_id" in params:
+            query_params.append(("requestId", params["request_id"]))  # noqa: E501
+        if "wait_until_task_ref" in params:
+            query_params.append(
+                ("waitUntilTaskRef", params["wait_until_task_ref"])
+            )  # noqa: E501
+        if "wait_for_seconds" in params:
+            query_params.append(
+                ("waitForSeconds", params["wait_for_seconds"])
+            )  # noqa: E501
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/workflow/{workflowId}/state",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="WorkflowRun",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/http/api_client.py b/omagent_core/engine/http/api_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..eaaa0546deadf218a9de9eae6618c4b0f5334245
--- /dev/null
+++ b/omagent_core/engine/http/api_client.py
@@ -0,0 +1,820 @@
+import datetime
+import logging
+import mimetypes
+import os
+import re
+import tempfile
+import time
+from typing import Dict
+
+import omagent_core.engine.http.models as http_models
+import six
+import urllib3
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http import rest
+from omagent_core.engine.http.rest import AuthorizationException
+from omagent_core.engine.http.thread import AwaitableThread
+from omagent_core.utils.container import container
+from requests.structures import CaseInsensitiveDict
+from six.moves.urllib.parse import quote
+
+logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+
+class ApiClient(object):
+    PRIMITIVE_TYPES = (float, bool, bytes, six.text_type) + six.integer_types
+    NATIVE_TYPES_MAPPING = {
+        "int": int,
+        "long": int if six.PY3 else long,  # noqa: F821
+        "float": float,
+        "str": str,
+        "bool": bool,
+        "date": datetime.date,
+        "datetime": datetime.datetime,
+        "object": object,
+    }
+
+    def __init__(
+        self, configuration=None, header_name=None, header_value=None, cookie=None
+    ):
+
+        self.rest_client = rest.RESTClientObject(
+            connection=container.conductor_config.http_connection
+        )
+
+        self.default_headers = self.__get_default_headers(header_name, header_value)
+
+        self.cookie = cookie
+        self.__refresh_auth_token()
+
+    def __call_api(
+        self,
+        resource_path,
+        method,
+        path_params=None,
+        query_params=None,
+        header_params=None,
+        body=None,
+        post_params=None,
+        files=None,
+        response_type=None,
+        auth_settings=None,
+        _return_http_data_only=None,
+        collection_formats=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        try:
+            return self.__call_api_no_retry(
+                resource_path=resource_path,
+                method=method,
+                path_params=path_params,
+                query_params=query_params,
+                header_params=header_params,
+                body=body,
+                post_params=post_params,
+                files=files,
+                response_type=response_type,
+                auth_settings=auth_settings,
+                _return_http_data_only=_return_http_data_only,
+                collection_formats=collection_formats,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+            )
+        except AuthorizationException as ae:
+            if ae.token_expired:
+                logger.error(
+                    f"authentication token has expired, refreshing the token.  request= {method} {resource_path}"
+                )
+                # if the token has expired, lets refresh the token
+                self.__force_refresh_auth_token()
+                # and now retry the same request
+                return self.__call_api_no_retry(
+                    resource_path=resource_path,
+                    method=method,
+                    path_params=path_params,
+                    query_params=query_params,
+                    header_params=header_params,
+                    body=body,
+                    post_params=post_params,
+                    files=files,
+                    response_type=response_type,
+                    auth_settings=auth_settings,
+                    _return_http_data_only=_return_http_data_only,
+                    collection_formats=collection_formats,
+                    _preload_content=_preload_content,
+                    _request_timeout=_request_timeout,
+                )
+            raise ae
+
+    def __call_api_no_retry(
+        self,
+        resource_path,
+        method,
+        path_params=None,
+        query_params=None,
+        header_params=None,
+        body=None,
+        post_params=None,
+        files=None,
+        response_type=None,
+        auth_settings=None,
+        _return_http_data_only=None,
+        collection_formats=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+
+        config = container.conductor_config
+
+        # header parameters
+        header_params = header_params or {}
+        header_params.update(self.default_headers)
+        if self.cookie:
+            header_params["Cookie"] = self.cookie
+        if header_params:
+            header_params = self.sanitize_for_serialization(header_params)
+            header_params = dict(
+                self.parameters_to_tuples(header_params, collection_formats)
+            )
+
+        # path parameters
+        if path_params:
+            path_params = self.sanitize_for_serialization(path_params)
+            path_params = self.parameters_to_tuples(path_params, collection_formats)
+            for k, v in path_params:
+                # specified safe chars, encode everything
+                resource_path = resource_path.replace(
+                    "{%s}" % k, quote(str(v), safe=config.safe_chars_for_path_param)
+                )
+
+        # query parameters
+        if query_params:
+            query_params = self.sanitize_for_serialization(query_params)
+            query_params = self.parameters_to_tuples(query_params, collection_formats)
+
+        # post parameters
+        if post_params or files:
+            post_params = self.prepare_post_parameters(post_params, files)
+            post_params = self.sanitize_for_serialization(post_params)
+            post_params = self.parameters_to_tuples(post_params, collection_formats)
+
+        # auth setting
+        auth_headers = None
+        if (
+            container.conductor_config.authentication_settings is not None
+            and resource_path != "/token"
+        ):
+            auth_headers = self.__get_authentication_headers()
+        self.update_params_for_auth(header_params, query_params, auth_headers)
+
+        # body
+        if body:
+            body = self.sanitize_for_serialization(body)
+
+        # request url
+        url = container.conductor_config.host + resource_path
+
+        # perform request and return response
+        response_data = self.request(
+            method,
+            url,
+            query_params=query_params,
+            headers=header_params,
+            post_params=post_params,
+            body=body,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+        )
+
+        self.last_response = response_data
+
+        return_data = response_data
+        if _preload_content:
+            # deserialize response data
+            if response_type:
+                return_data = self.deserialize(response_data, response_type)
+            else:
+                return_data = None
+
+        if _return_http_data_only:
+            return return_data
+        else:
+            return (return_data, response_data.status, response_data.getheaders())
+
+    def sanitize_for_serialization(self, obj):
+        """Builds a JSON POST object.
+
+        If obj is None, return None.
+        If obj is str, int, long, float, bool, return directly.
+        If obj is datetime.datetime, datetime.date
+            convert to string in iso8601 format.
+        If obj is list, sanitize each element in the list.
+        If obj is dict, return the dict.
+        If obj is swagger model, return the properties dict.
+
+        :param obj: The data to serialize.
+        :return: The serialized form of data.
+        """
+        if obj is None:
+            return None
+        elif isinstance(obj, self.PRIMITIVE_TYPES):
+            return obj
+        elif isinstance(obj, list):
+            return [self.sanitize_for_serialization(sub_obj) for sub_obj in obj]
+        elif isinstance(obj, tuple):
+            return tuple(self.sanitize_for_serialization(sub_obj) for sub_obj in obj)
+        elif isinstance(obj, (datetime.datetime, datetime.date)):
+            return obj.isoformat()
+
+        if isinstance(obj, dict) or isinstance(obj, CaseInsensitiveDict):
+            obj_dict = obj
+        else:
+            # Convert model obj to dict except
+            # attributes `swagger_types`, `attribute_map`
+            # and attributes which value is not None.
+            # Convert attribute name to json key in
+            # model definition for request.
+            if hasattr(obj, "attribute_map") and hasattr(obj, "swagger_types"):
+                obj_dict = {
+                    obj.attribute_map[attr]: getattr(obj, attr)
+                    for attr, _ in six.iteritems(obj.swagger_types)
+                    if getattr(obj, attr) is not None
+                }
+            else:
+                obj_dict = {
+                    name: getattr(obj, name)
+                    for name in vars(obj)
+                    if getattr(obj, name) is not None
+                }
+
+        return {
+            key: self.sanitize_for_serialization(val)
+            for key, val in six.iteritems(obj_dict)
+        }
+
+    def deserialize(self, response, response_type):
+        """Deserializes response into an object.
+
+        :param response: RESTResponse object to be deserialized.
+        :param response_type: class literal for
+            deserialized object, or string of class name.
+
+        :return: deserialized object.
+        """
+        # handle file downloading
+        # save response body into a tmp file and return the instance
+        if response_type == "file":
+            return self.__deserialize_file(response)
+
+        # fetch data from response object
+        try:
+            data = response.resp.json()
+        except Exception:
+            data = response.resp.text
+
+        try:
+            return self.__deserialize(data, response_type)
+        except ValueError as e:
+            logger.error(
+                f"failed to deserialize data {data} into class {response_type}, reason: {e}"
+            )
+            return None
+
+    def deserialize_class(self, data, klass):
+        return self.__deserialize(data, klass)
+
+    def __deserialize(self, data, klass):
+        """Deserializes dict, list, str into an object.
+
+        :param data: dict, list or str.
+        :param klass: class literal, or string of class name.
+
+        :return: object.
+        """
+        if data is None:
+            return None
+
+        if type(klass) == str:
+            if klass.startswith("list["):
+                sub_kls = re.match(r"list\[(.*)\]", klass).group(1)
+                return [self.__deserialize(sub_data, sub_kls) for sub_data in data]
+
+            if klass.startswith("dict("):
+                sub_kls = re.match(r"dict\(([^,]*), (.*)\)", klass).group(2)
+                return {
+                    k: self.__deserialize(v, sub_kls) for k, v in six.iteritems(data)
+                }
+
+            # convert str to class
+            if klass in self.NATIVE_TYPES_MAPPING:
+                klass = self.NATIVE_TYPES_MAPPING[klass]
+            else:
+                klass = getattr(http_models, klass)
+
+        if klass in self.PRIMITIVE_TYPES:
+            return self.__deserialize_primitive(data, klass)
+        elif klass == object:
+            return self.__deserialize_object(data)
+        elif klass == datetime.date:
+            return self.__deserialize_date(data)
+        elif klass == datetime.datetime:
+            return self.__deserialize_datatime(data)
+        else:
+            return self.__deserialize_model(data, klass)
+
+    def call_api(
+        self,
+        resource_path,
+        method,
+        path_params=None,
+        query_params=None,
+        header_params=None,
+        body=None,
+        post_params=None,
+        files=None,
+        response_type=None,
+        auth_settings=None,
+        async_req=None,
+        _return_http_data_only=None,
+        collection_formats=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        """Makes the HTTP request (synchronous) and returns deserialized data.
+
+        To make an async request, set the async_req parameter.
+
+        :param resource_path: Path to method endpoint.
+        :param method: Method to call.
+        :param path_params: Path parameters in the url.
+        :param query_params: Query parameters in the url.
+        :param header_params: Header parameters to be
+            placed in the request header.
+        :param body: Request body.
+        :param post_params dict: Request post form parameters,
+            for `application/x-www-form-urlencoded`, `multipart/form-data`.
+        :param auth_settings list: Auth Settings names for the request.
+        :param response: Response data type.
+        :param files dict: key -> filename, value -> filepath,
+            for `multipart/form-data`.
+        :param async_req bool: execute request asynchronously
+        :param _return_http_data_only: response data without head status code
+                                       and headers
+        :param collection_formats: dict of collection formats for path, query,
+            header, and post parameters.
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        :return:
+            If async_req parameter is True,
+            the request will be called asynchronously.
+            The method will return the request thread.
+            If parameter async_req is False or missing,
+            then the method will return the response directly.
+        """
+        if not async_req:
+            return self.__call_api(
+                resource_path,
+                method,
+                path_params,
+                query_params,
+                header_params,
+                body,
+                post_params,
+                files,
+                response_type,
+                auth_settings,
+                _return_http_data_only,
+                collection_formats,
+                _preload_content,
+                _request_timeout,
+            )
+        thread = AwaitableThread(
+            target=self.__call_api,
+            args=(
+                resource_path,
+                method,
+                path_params,
+                query_params,
+                header_params,
+                body,
+                post_params,
+                files,
+                response_type,
+                auth_settings,
+                _return_http_data_only,
+                collection_formats,
+                _preload_content,
+                _request_timeout,
+            ),
+        )
+        thread.start()
+        return thread
+
+    def request(
+        self,
+        method,
+        url,
+        query_params=None,
+        headers=None,
+        post_params=None,
+        body=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        """Makes the HTTP request using RESTClient."""
+        if method == "GET":
+            return self.rest_client.GET(
+                url,
+                query_params=query_params,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+                headers=headers,
+            )
+        elif method == "HEAD":
+            return self.rest_client.HEAD(
+                url,
+                query_params=query_params,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+                headers=headers,
+            )
+        elif method == "OPTIONS":
+            return self.rest_client.OPTIONS(
+                url,
+                query_params=query_params,
+                headers=headers,
+                post_params=post_params,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+                body=body,
+            )
+        elif method == "POST":
+            return self.rest_client.POST(
+                url,
+                query_params=query_params,
+                headers=headers,
+                post_params=post_params,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+                body=body,
+            )
+        elif method == "PUT":
+            return self.rest_client.PUT(
+                url,
+                query_params=query_params,
+                headers=headers,
+                post_params=post_params,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+                body=body,
+            )
+        elif method == "PATCH":
+            return self.rest_client.PATCH(
+                url,
+                query_params=query_params,
+                headers=headers,
+                post_params=post_params,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+                body=body,
+            )
+        elif method == "DELETE":
+            return self.rest_client.DELETE(
+                url,
+                query_params=query_params,
+                headers=headers,
+                _preload_content=_preload_content,
+                _request_timeout=_request_timeout,
+                body=body,
+            )
+        else:
+            raise ValueError(
+                "http method must be `GET`, `HEAD`, `OPTIONS`,"
+                " `POST`, `PATCH`, `PUT` or `DELETE`."
+            )
+
+    def parameters_to_tuples(self, params, collection_formats):
+        """Get parameters as list of tuples, formatting collections.
+
+        :param params: Parameters as dict or list of two-tuples
+        :param dict collection_formats: Parameter collection formats
+        :return: Parameters as list of tuples, collections formatted
+        """
+        new_params = []
+        if collection_formats is None:
+            collection_formats = {}
+        for k, v in (
+            six.iteritems(params) if isinstance(params, dict) else params
+        ):  # noqa: E501
+            if k in collection_formats:
+                collection_format = collection_formats[k]
+                if collection_format == "multi":
+                    new_params.extend((k, value) for value in v)
+                else:
+                    if collection_format == "ssv":
+                        delimiter = " "
+                    elif collection_format == "tsv":
+                        delimiter = "\t"
+                    elif collection_format == "pipes":
+                        delimiter = "|"
+                    else:  # csv is the default
+                        delimiter = ","
+                    new_params.append((k, delimiter.join(str(value) for value in v)))
+            else:
+                new_params.append((k, v))
+        return new_params
+
+    def prepare_post_parameters(self, post_params=None, files=None):
+        """Builds form parameters.
+
+        :param post_params: Normal form parameters.
+        :param files: File parameters.
+        :return: Form parameters with files.
+        """
+        params = []
+
+        if post_params:
+            params = post_params
+
+        if files:
+            for k, v in six.iteritems(files):
+                if not v:
+                    continue
+                file_names = v if type(v) is list else [v]
+                for n in file_names:
+                    with open(n, "rb") as f:
+                        filename = os.path.basename(f.name)
+                        filedata = f.read()
+                        mimetype = (
+                            mimetypes.guess_type(filename)[0]
+                            or "application/octet-stream"
+                        )
+                        params.append(tuple([k, tuple([filename, filedata, mimetype])]))
+
+        return params
+
+    def select_header_accept(self, accepts):
+        """Returns `Accept` based on an array of accepts provided.
+
+        :param accepts: List of headers.
+        :return: Accept (e.g. application/json).
+        """
+        if not accepts:
+            return
+
+        accepts = [x.lower() for x in accepts]
+
+        if "application/json" in accepts:
+            return "application/json"
+        else:
+            return ", ".join(accepts)
+
+    def select_header_content_type(self, content_types):
+        """Returns `Content-Type` based on an array of content_types provided.
+
+        :param content_types: List of content-types.
+        :return: Content-Type (e.g. application/json).
+        """
+        if not content_types:
+            return "application/json"
+
+        content_types = [x.lower() for x in content_types]
+
+        if "application/json" in content_types or "*/*" in content_types:
+            return "application/json"
+        else:
+            return content_types[0]
+
+    def update_params_for_auth(self, headers, querys, auth_settings):
+        """Updates header and query params based on authentication setting.
+
+        :param headers: Header parameters dict to be updated.
+        :param querys: Query parameters tuple list to be updated.
+        :param auth_settings: Authentication setting identifiers list.
+        """
+        if not auth_settings:
+            return
+
+        if "header" in auth_settings:
+            for key, value in auth_settings["header"].items():
+                headers[key] = value
+        if "query" in auth_settings:
+            for key, value in auth_settings["query"].items():
+                querys[key] = value
+
+    def __deserialize_file(self, response):
+        """Deserializes body to file
+
+        Saves response body into a file in a temporary folder,
+        using the filename from the `Content-Disposition` header if provided.
+
+        :param response:  RESTResponse.
+        :return: file path.
+        """
+        fd, path = tempfile.mkstemp(dir=container.conductor_config.temp_folder_path)
+        os.close(fd)
+        os.remove(path)
+
+        content_disposition = response.getheader("Content-Disposition")
+        if content_disposition:
+            filename = re.search(
+                r'filename=[\'"]?([^\'"\s]+)[\'"]?', content_disposition
+            ).group(1)
+            path = os.path.join(os.path.dirname(path), filename)
+            response_data = response.data
+            with open(path, "wb") as f:
+                if isinstance(response_data, str):
+                    # change str to bytes so we can write it
+                    response_data = response_data.encode("utf-8")
+                    f.write(response_data)
+                else:
+                    f.write(response_data)
+        return path
+
+    def __deserialize_primitive(self, data, klass):
+        """Deserializes string to primitive type.
+
+        :param data: str.
+        :param klass: class literal.
+
+        :return: int, long, float, str, bool.
+        """
+        try:
+            if klass == str and type(data) == bytes:
+                return self.__deserialize_bytes_to_str(data)
+            return klass(data)
+        except UnicodeEncodeError:
+            return six.text_type(data)
+        except TypeError:
+            return data
+
+    def __deserialize_bytes_to_str(self, data):
+        return data.decode("utf-8")
+
+    def __deserialize_object(self, value):
+        """Return a original value.
+
+        :return: object.
+        """
+        return value
+
+    def __deserialize_date(self, string):
+        """Deserializes string to date.
+
+        :param string: str.
+        :return: date.
+        """
+        try:
+            from dateutil.parser import parse
+
+            return parse(string).date()
+        except ImportError:
+            return string
+        except ValueError:
+            raise rest.ApiException(
+                status=0, reason="Failed to parse `{0}` as date object".format(string)
+            )
+
+    def __deserialize_datatime(self, string):
+        """Deserializes string to datetime.
+
+        The string should be in iso8601 datetime format.
+
+        :param string: str.
+        :return: datetime.
+        """
+        try:
+            from dateutil.parser import parse
+
+            return parse(string)
+        except ImportError:
+            return string
+        except ValueError:
+            raise rest.ApiException(
+                status=0,
+                reason=("Failed to parse `{0}` as datetime object".format(string)),
+            )
+
+    def __hasattr(self, object, name):
+        return name in object.__class__.__dict__
+
+    def __deserialize_model(self, data, klass):
+        """Deserializes list or dict to model.
+
+        :param data: dict, list.
+        :param klass: class literal.
+        :return: model object.
+        """
+        if not klass.swagger_types and not self.__hasattr(
+            klass, "get_real_child_model"
+        ):
+            return data
+
+        kwargs = {}
+        if klass.swagger_types is not None:
+            for attr, attr_type in six.iteritems(klass.swagger_types):
+                if (
+                    data is not None
+                    and klass.attribute_map[attr] in data
+                    and isinstance(data, (list, dict))
+                ):
+                    value = data[klass.attribute_map[attr]]
+                    kwargs[attr] = self.__deserialize(value, attr_type)
+
+        instance = klass(**kwargs)
+
+        if (
+            isinstance(instance, dict)
+            and klass.swagger_types is not None
+            and isinstance(data, dict)
+        ):
+            for key, value in data.items():
+                if key not in klass.swagger_types:
+                    instance[key] = value
+        if self.__hasattr(instance, "get_real_child_model"):
+            klass_name = instance.get_real_child_model(data)
+            if klass_name:
+                instance = self.__deserialize(data, klass_name)
+        return instance
+
+    def __get_authentication_headers(self):
+        if container.conductor_config.AUTH_TOKEN is None:
+            return None
+
+        now = round(time.time() * 1000)
+        time_since_last_update = now - container.conductor_config.token_update_time
+
+        if time_since_last_update > container.conductor_config.auth_token_ttl_msec:
+            # time to refresh the token
+            logger.debug(f"refreshing authentication token")
+            token = self.__get_new_token()
+            container.conductor_config.update_token(token)
+
+        return {"header": {"X-Authorization": container.conductor_config.AUTH_TOKEN}}
+
+    def __refresh_auth_token(self) -> None:
+        if container.conductor_config.AUTH_TOKEN is not None:
+            return
+        if container.conductor_config.authentication_settings is None:
+            return
+        token = self.__get_new_token()
+        container.conductor_config.update_token(token)
+
+    def __force_refresh_auth_token(self) -> None:
+        """
+        Forces the token refresh.  Unlike the __refresh_auth_token method above
+        """
+        if container.conductor_config.authentication_settings is None:
+            return
+        token = self.__get_new_token()
+        container.conductor_config.update_token(token)
+
+    def __get_new_token(self) -> str:
+        try:
+            if (
+                container.conductor_config.authentication_settings.key_id is None
+                or container.conductor_config.authentication_settings.key_secret is None
+            ):
+                logger.error(
+                    "Authentication Key or Secret is set.  Failed to get the auth token"
+                )
+                return None
+
+            response = self.call_api(
+                "/token",
+                "POST",
+                header_params={
+                    "Content-Type": self.select_header_content_type(["*/*"])
+                },
+                body={
+                    "keyId": container.conductor_config.authentication_settings.key_id,
+                    "keySecret": container.conductor_config.authentication_settings.key_secret,
+                },
+                _return_http_data_only=True,
+                response_type="Token",
+            )
+            return response.token
+        except Exception as e:
+            logger.error(f"Failed to get new token, reason: {e.args}")
+            return None
+
+    def __get_default_headers(
+        self, header_name: str, header_value: object
+    ) -> Dict[str, object]:
+        headers = {
+            "Accept-Encoding": "gzip",
+        }
+        if header_name is not None:
+            headers[header_name] = header_value
+        parsed = urllib3.util.parse_url(container.conductor_config.host)
+        if parsed.auth is not None:
+            encrypted_headers = urllib3.util.make_headers(basic_auth=parsed.auth)
+            for key, value in encrypted_headers.items():
+                headers[key] = value
+        return headers
diff --git a/omagent_core/engine/http/models/__init__.py b/omagent_core/engine/http/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d2c2d19452a0bab0d62144749893a9e62a62c3d
--- /dev/null
+++ b/omagent_core/engine/http/models/__init__.py
@@ -0,0 +1,72 @@
+from omagent_core.engine.http.models.action import Action
+from omagent_core.engine.http.models.authorization_request import \
+    AuthorizationRequest
+from omagent_core.engine.http.models.bulk_response import BulkResponse
+from omagent_core.engine.http.models.conductor_application import \
+    ConductorApplication
+from omagent_core.engine.http.models.conductor_user import ConductorUser
+from omagent_core.engine.http.models.create_or_update_application_request import \
+    CreateOrUpdateApplicationRequest
+from omagent_core.engine.http.models.event_handler import EventHandler
+from omagent_core.engine.http.models.external_storage_location import \
+    ExternalStorageLocation
+from omagent_core.engine.http.models.generate_token_request import \
+    GenerateTokenRequest
+from omagent_core.engine.http.models.group import Group
+from omagent_core.engine.http.models.integration import Integration
+from omagent_core.engine.http.models.integration_api import IntegrationApi
+from omagent_core.engine.http.models.permission import Permission
+from omagent_core.engine.http.models.poll_data import PollData
+from omagent_core.engine.http.models.prompt_template import PromptTemplate
+from omagent_core.engine.http.models.rate_limit import RateLimit
+from omagent_core.engine.http.models.rerun_workflow_request import \
+    RerunWorkflowRequest
+from omagent_core.engine.http.models.response import Response
+from omagent_core.engine.http.models.role import Role
+from omagent_core.engine.http.models.save_schedule_request import \
+    SaveScheduleRequest
+from omagent_core.engine.http.models.scrollable_search_result_workflow_summary import \
+    ScrollableSearchResultWorkflowSummary
+from omagent_core.engine.http.models.search_result_task import SearchResultTask
+from omagent_core.engine.http.models.search_result_task_summary import \
+    SearchResultTaskSummary
+from omagent_core.engine.http.models.search_result_workflow import \
+    SearchResultWorkflow
+from omagent_core.engine.http.models.search_result_workflow_schedule_execution_model import \
+    SearchResultWorkflowScheduleExecutionModel
+from omagent_core.engine.http.models.search_result_workflow_summary import \
+    SearchResultWorkflowSummary
+from omagent_core.engine.http.models.skip_task_request import SkipTaskRequest
+from omagent_core.engine.http.models.start_workflow import StartWorkflow
+from omagent_core.engine.http.models.start_workflow_request import (
+    IdempotencyStrategy, StartWorkflowRequest)
+from omagent_core.engine.http.models.state_change_event import (
+    StateChangeConfig, StateChangeEvent, StateChangeEventType)
+from omagent_core.engine.http.models.sub_workflow_params import \
+    SubWorkflowParams
+from omagent_core.engine.http.models.subject_ref import SubjectRef
+from omagent_core.engine.http.models.tag_object import TagObject
+from omagent_core.engine.http.models.tag_string import TagString
+from omagent_core.engine.http.models.target_ref import TargetRef
+from omagent_core.engine.http.models.task import Task
+from omagent_core.engine.http.models.task_def import TaskDef
+from omagent_core.engine.http.models.task_details import TaskDetails
+from omagent_core.engine.http.models.task_exec_log import TaskExecLog
+from omagent_core.engine.http.models.task_result import TaskResult
+from omagent_core.engine.http.models.task_summary import TaskSummary
+from omagent_core.engine.http.models.token import Token
+from omagent_core.engine.http.models.upsert_group_request import \
+    UpsertGroupRequest
+from omagent_core.engine.http.models.upsert_user_request import \
+    UpsertUserRequest
+from omagent_core.engine.http.models.workflow import Workflow
+from omagent_core.engine.http.models.workflow_def import WorkflowDef
+from omagent_core.engine.http.models.workflow_run import WorkflowRun
+from omagent_core.engine.http.models.workflow_schedule import WorkflowSchedule
+from omagent_core.engine.http.models.workflow_schedule_execution_model import \
+    WorkflowScheduleExecutionModel
+from omagent_core.engine.http.models.workflow_status import WorkflowStatus
+from omagent_core.engine.http.models.workflow_summary import WorkflowSummary
+from omagent_core.engine.http.models.workflow_tag import WorkflowTag
+from omagent_core.engine.http.models.workflow_task import (CacheConfig,
+                                                           WorkflowTask)
diff --git a/omagent_core/engine/http/models/action.py b/omagent_core/engine/http/models/action.py
new file mode 100644
index 0000000000000000000000000000000000000000..4dd6b6993b88d3732b889317c962b46ff7ffffaa
--- /dev/null
+++ b/omagent_core/engine/http/models/action.py
@@ -0,0 +1,222 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class Action(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "action": "str",
+        "start_workflow": "StartWorkflow",
+        "complete_task": "TaskDetails",
+        "fail_task": "TaskDetails",
+        "expand_inline_json": "bool",
+    }
+
+    attribute_map = {
+        "action": "action",
+        "start_workflow": "start_workflow",
+        "complete_task": "complete_task",
+        "fail_task": "fail_task",
+        "expand_inline_json": "expandInlineJSON",
+    }
+
+    def __init__(
+        self,
+        action=None,
+        start_workflow=None,
+        complete_task=None,
+        fail_task=None,
+        expand_inline_json=None,
+    ):  # noqa: E501
+        """Action - a model defined in Swagger"""  # noqa: E501
+        self._action = None
+        self._start_workflow = None
+        self._complete_task = None
+        self._fail_task = None
+        self._expand_inline_json = None
+        self.discriminator = None
+        if action is not None:
+            self.action = action
+        if start_workflow is not None:
+            self.start_workflow = start_workflow
+        if complete_task is not None:
+            self.complete_task = complete_task
+        if fail_task is not None:
+            self.fail_task = fail_task
+        if expand_inline_json is not None:
+            self.expand_inline_json = expand_inline_json
+
+    @property
+    def action(self):
+        """Gets the action of this Action.  # noqa: E501
+
+
+        :return: The action of this Action.  # noqa: E501
+        :rtype: str
+        """
+        return self._action
+
+    @action.setter
+    def action(self, action):
+        """Sets the action of this Action.
+
+
+        :param action: The action of this Action.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["start_workflow", "complete_task", "fail_task"]  # noqa: E501
+        if action not in allowed_values:
+            raise ValueError(
+                "Invalid value for `action` ({0}), must be one of {1}".format(  # noqa: E501
+                    action, allowed_values
+                )
+            )
+
+        self._action = action
+
+    @property
+    def start_workflow(self):
+        """Gets the start_workflow of this Action.  # noqa: E501
+
+
+        :return: The start_workflow of this Action.  # noqa: E501
+        :rtype: StartWorkflow
+        """
+        return self._start_workflow
+
+    @start_workflow.setter
+    def start_workflow(self, start_workflow):
+        """Sets the start_workflow of this Action.
+
+
+        :param start_workflow: The start_workflow of this Action.  # noqa: E501
+        :type: StartWorkflow
+        """
+
+        self._start_workflow = start_workflow
+
+    @property
+    def complete_task(self):
+        """Gets the complete_task of this Action.  # noqa: E501
+
+
+        :return: The complete_task of this Action.  # noqa: E501
+        :rtype: TaskDetails
+        """
+        return self._complete_task
+
+    @complete_task.setter
+    def complete_task(self, complete_task):
+        """Sets the complete_task of this Action.
+
+
+        :param complete_task: The complete_task of this Action.  # noqa: E501
+        :type: TaskDetails
+        """
+
+        self._complete_task = complete_task
+
+    @property
+    def fail_task(self):
+        """Gets the fail_task of this Action.  # noqa: E501
+
+
+        :return: The fail_task of this Action.  # noqa: E501
+        :rtype: TaskDetails
+        """
+        return self._fail_task
+
+    @fail_task.setter
+    def fail_task(self, fail_task):
+        """Sets the fail_task of this Action.
+
+
+        :param fail_task: The fail_task of this Action.  # noqa: E501
+        :type: TaskDetails
+        """
+
+        self._fail_task = fail_task
+
+    @property
+    def expand_inline_json(self):
+        """Gets the expand_inline_json of this Action.  # noqa: E501
+
+
+        :return: The expand_inline_json of this Action.  # noqa: E501
+        :rtype: bool
+        """
+        return self._expand_inline_json
+
+    @expand_inline_json.setter
+    def expand_inline_json(self, expand_inline_json):
+        """Sets the expand_inline_json of this Action.
+
+
+        :param expand_inline_json: The expand_inline_json of this Action.  # noqa: E501
+        :type: bool
+        """
+
+        self._expand_inline_json = expand_inline_json
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Action, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Action):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/authorization_request.py b/omagent_core/engine/http/models/authorization_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..30b230dc5d1882b95c4f2ab1003eefd2a6ee528f
--- /dev/null
+++ b/omagent_core/engine/http/models/authorization_request.py
@@ -0,0 +1,159 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class AuthorizationRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "subject": "SubjectRef",
+        "target": "TargetRef",
+        "access": "list[str]",
+    }
+
+    attribute_map = {"subject": "subject", "target": "target", "access": "access"}
+
+    def __init__(self, subject=None, target=None, access=None):  # noqa: E501
+        """AuthorizationRequest - a model defined in Swagger"""  # noqa: E501
+        self._subject = None
+        self._target = None
+        self._access = None
+        self.discriminator = None
+        self.subject = subject
+        self.target = target
+        self.access = access
+
+    @property
+    def subject(self):
+        """Gets the subject of this AuthorizationRequest.  # noqa: E501
+
+
+        :return: The subject of this AuthorizationRequest.  # noqa: E501
+        :rtype: SubjectRef
+        """
+        return self._subject
+
+    @subject.setter
+    def subject(self, subject):
+        """Sets the subject of this AuthorizationRequest.
+
+
+        :param subject: The subject of this AuthorizationRequest.  # noqa: E501
+        :type: SubjectRef
+        """
+        self._subject = subject
+
+    @property
+    def target(self):
+        """Gets the target of this AuthorizationRequest.  # noqa: E501
+
+
+        :return: The target of this AuthorizationRequest.  # noqa: E501
+        :rtype: TargetRef
+        """
+        return self._target
+
+    @target.setter
+    def target(self, target):
+        """Sets the target of this AuthorizationRequest.
+
+
+        :param target: The target of this AuthorizationRequest.  # noqa: E501
+        :type: TargetRef
+        """
+        self._target = target
+
+    @property
+    def access(self):
+        """Gets the access of this AuthorizationRequest.  # noqa: E501
+
+        The set of access which is granted or removed  # noqa: E501
+
+        :return: The access of this AuthorizationRequest.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._access
+
+    @access.setter
+    def access(self, access):
+        """Sets the access of this AuthorizationRequest.
+
+        The set of access which is granted or removed  # noqa: E501
+
+        :param access: The access of this AuthorizationRequest.  # noqa: E501
+        :type: list[str]
+        """
+        allowed_values = ["CREATE", "READ", "UPDATE", "DELETE", "EXECUTE"]  # noqa: E501
+        if not set(access).issubset(set(allowed_values)):
+            raise ValueError(
+                "Invalid values for `access` [{0}], must be a subset of [{1}]".format(  # noqa: E501
+                    ", ".join(
+                        map(str, set(access) - set(allowed_values))
+                    ),  # noqa: E501
+                    ", ".join(map(str, allowed_values)),
+                )
+            )
+
+        self._access = access
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(AuthorizationRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, AuthorizationRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/bulk_response.py b/omagent_core/engine/http/models/bulk_response.py
new file mode 100644
index 0000000000000000000000000000000000000000..acabc4ddeda5e71f2a741c1f54f2aebf4e029524
--- /dev/null
+++ b/omagent_core/engine/http/models/bulk_response.py
@@ -0,0 +1,132 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class BulkResponse(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "bulk_error_results": "dict(str, str)",
+        "bulk_successful_results": "list[str]",
+    }
+
+    attribute_map = {
+        "bulk_error_results": "bulkErrorResults",
+        "bulk_successful_results": "bulkSuccessfulResults",
+    }
+
+    def __init__(
+        self, bulk_error_results=None, bulk_successful_results=None
+    ):  # noqa: E501
+        """BulkResponse - a model defined in Swagger"""  # noqa: E501
+        self._bulk_error_results = None
+        self._bulk_successful_results = None
+        self.discriminator = None
+        if bulk_error_results is not None:
+            self.bulk_error_results = bulk_error_results
+        if bulk_successful_results is not None:
+            self.bulk_successful_results = bulk_successful_results
+
+    @property
+    def bulk_error_results(self):
+        """Gets the bulk_error_results of this BulkResponse.  # noqa: E501
+
+
+        :return: The bulk_error_results of this BulkResponse.  # noqa: E501
+        :rtype: dict(str, str)
+        """
+        return self._bulk_error_results
+
+    @bulk_error_results.setter
+    def bulk_error_results(self, bulk_error_results):
+        """Sets the bulk_error_results of this BulkResponse.
+
+
+        :param bulk_error_results: The bulk_error_results of this BulkResponse.  # noqa: E501
+        :type: dict(str, str)
+        """
+
+        self._bulk_error_results = bulk_error_results
+
+    @property
+    def bulk_successful_results(self):
+        """Gets the bulk_successful_results of this BulkResponse.  # noqa: E501
+
+
+        :return: The bulk_successful_results of this BulkResponse.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._bulk_successful_results
+
+    @bulk_successful_results.setter
+    def bulk_successful_results(self, bulk_successful_results):
+        """Sets the bulk_successful_results of this BulkResponse.
+
+
+        :param bulk_successful_results: The bulk_successful_results of this BulkResponse.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._bulk_successful_results = bulk_successful_results
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(BulkResponse, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, BulkResponse):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/conductor_application.py b/omagent_core/engine/http/models/conductor_application.py
new file mode 100644
index 0000000000000000000000000000000000000000..ec1249faef0f48a4633ead30b67eb92bf81a2ae3
--- /dev/null
+++ b/omagent_core/engine/http/models/conductor_application.py
@@ -0,0 +1,148 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class ConductorApplication(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"id": "str", "name": "str", "created_by": "str"}
+
+    attribute_map = {"id": "id", "name": "name", "created_by": "createdBy"}
+
+    def __init__(self, id=None, name=None, created_by=None):  # noqa: E501
+        """ConductorApplication - a model defined in Swagger"""  # noqa: E501
+        self._id = None
+        self._name = None
+        self._created_by = None
+        self.discriminator = None
+        if id is not None:
+            self.id = id
+        if name is not None:
+            self.name = name
+        if created_by is not None:
+            self.created_by = created_by
+
+    @property
+    def id(self):
+        """Gets the id of this ConductorApplication.  # noqa: E501
+
+
+        :return: The id of this ConductorApplication.  # noqa: E501
+        :rtype: str
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this ConductorApplication.
+
+
+        :param id: The id of this ConductorApplication.  # noqa: E501
+        :type: str
+        """
+
+        self._id = id
+
+    @property
+    def name(self):
+        """Gets the name of this ConductorApplication.  # noqa: E501
+
+
+        :return: The name of this ConductorApplication.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this ConductorApplication.
+
+
+        :param name: The name of this ConductorApplication.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this ConductorApplication.  # noqa: E501
+
+
+        :return: The created_by of this ConductorApplication.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this ConductorApplication.
+
+
+        :param created_by: The created_by of this ConductorApplication.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(ConductorApplication, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, ConductorApplication):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/conductor_user.py b/omagent_core/engine/http/models/conductor_user.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d8aa8a41a89077b40f7b6c56f2e27cc79c7ad49
--- /dev/null
+++ b/omagent_core/engine/http/models/conductor_user.py
@@ -0,0 +1,296 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class ConductorUser(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "id": "str",
+        "name": "str",
+        "roles": "list[Role]",
+        "groups": "list[Group]",
+        "uuid": "str",
+        "application_user": "bool",
+        "encrypted_id": "bool",
+        "encrypted_id_display_value": "str",
+    }
+
+    attribute_map = {
+        "id": "id",
+        "name": "name",
+        "roles": "roles",
+        "groups": "groups",
+        "uuid": "uuid",
+        "application_user": "applicationUser",
+        "encrypted_id": "encryptedId",
+        "encrypted_id_display_value": "encryptedIdDisplayValue",
+    }
+
+    def __init__(
+        self,
+        id=None,
+        name=None,
+        roles=None,
+        groups=None,
+        uuid=None,
+        application_user=None,
+        encrypted_id=None,
+        encrypted_id_display_value=None,
+    ):  # noqa: E501
+        """ConductorUser - a model defined in Swagger"""  # noqa: E501
+        self._id = None
+        self._name = None
+        self._roles = None
+        self._groups = None
+        self._uuid = None
+        self._application_user = None
+        self._encrypted_id = None
+        self._encrypted_id_display_value = None
+        self.discriminator = None
+        if id is not None:
+            self.id = id
+        if name is not None:
+            self.name = name
+        if roles is not None:
+            self.roles = roles
+        if groups is not None:
+            self.groups = groups
+        if uuid is not None:
+            self.uuid = uuid
+        if application_user is not None:
+            self.application_user = application_user
+        if encrypted_id is not None:
+            self.encrypted_id = encrypted_id
+        if encrypted_id_display_value is not None:
+            self.encrypted_id_display_value = encrypted_id_display_value
+
+    @property
+    def id(self):
+        """Gets the id of this ConductorUser.  # noqa: E501
+
+
+        :return: The id of this ConductorUser.  # noqa: E501
+        :rtype: str
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this ConductorUser.
+
+
+        :param id: The id of this ConductorUser.  # noqa: E501
+        :type: str
+        """
+
+        self._id = id
+
+    @property
+    def name(self):
+        """Gets the name of this ConductorUser.  # noqa: E501
+
+
+        :return: The name of this ConductorUser.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this ConductorUser.
+
+
+        :param name: The name of this ConductorUser.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def roles(self):
+        """Gets the roles of this ConductorUser.  # noqa: E501
+
+
+        :return: The roles of this ConductorUser.  # noqa: E501
+        :rtype: list[Role]
+        """
+        return self._roles
+
+    @roles.setter
+    def roles(self, roles):
+        """Sets the roles of this ConductorUser.
+
+
+        :param roles: The roles of this ConductorUser.  # noqa: E501
+        :type: list[Role]
+        """
+
+        self._roles = roles
+
+    @property
+    def groups(self):
+        """Gets the groups of this ConductorUser.  # noqa: E501
+
+
+        :return: The groups of this ConductorUser.  # noqa: E501
+        :rtype: list[Group]
+        """
+        return self._groups
+
+    @groups.setter
+    def groups(self, groups):
+        """Sets the groups of this ConductorUser.
+
+
+        :param groups: The groups of this ConductorUser.  # noqa: E501
+        :type: list[Group]
+        """
+
+        self._groups = groups
+
+    @property
+    def uuid(self):
+        """Gets the uuid of this ConductorUser.  # noqa: E501
+
+
+        :return: The uuid of this ConductorUser.  # noqa: E501
+        :rtype: str
+        """
+        return self._uuid
+
+    @uuid.setter
+    def uuid(self, uuid):
+        """Sets the uuid of this ConductorUser.
+
+
+        :param uuid: The uuid of this ConductorUser.  # noqa: E501
+        :type: str
+        """
+
+        self._uuid = uuid
+
+    @property
+    def application_user(self):
+        """Gets the application_user of this ConductorUser.  # noqa: E501
+
+
+        :return: The application_user of this ConductorUser.  # noqa: E501
+        :rtype: bool
+        """
+        return self._application_user
+
+    @application_user.setter
+    def application_user(self, application_user):
+        """Sets the application_user of this ConductorUser.
+
+
+        :param application_user: The application_user of this ConductorUser.  # noqa: E501
+        :type: bool
+        """
+
+        self._application_user = application_user
+
+    @property
+    def encrypted_id(self):
+        """Gets the encrypted_id of this ConductorUser.  # noqa: E501
+
+
+        :return: The encrypted_id of this ConductorUser.  # noqa: E501
+        :rtype: bool
+        """
+        return self._encrypted_id
+
+    @encrypted_id.setter
+    def encrypted_id(self, encrypted_id):
+        """Sets the encrypted_id of this ConductorUser.
+
+
+        :param encrypted_id: The encrypted_id of this ConductorUser.  # noqa: E501
+        :type: bool
+        """
+
+        self._encrypted_id = encrypted_id
+
+    @property
+    def encrypted_id_display_value(self):
+        """Gets the encrypted_id_display_value of this ConductorUser.  # noqa: E501
+
+
+        :return: The encrypted_id_display_value of this ConductorUser.  # noqa: E501
+        :rtype: str
+        """
+        return self._encrypted_id_display_value
+
+    @encrypted_id_display_value.setter
+    def encrypted_id_display_value(self, encrypted_id_display_value):
+        """Sets the encrypted_id_display_value of this ConductorUser.
+
+
+        :param encrypted_id_display_value: The encrypted_id_display_value of this ConductorUser.  # noqa: E501
+        :type: str
+        """
+
+        self._encrypted_id_display_value = encrypted_id_display_value
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(ConductorUser, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, ConductorUser):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/correlation_ids_search_request.py b/omagent_core/engine/http/models/correlation_ids_search_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..6e6f5534c1e9d2cdc104e13bef5a4002105d73a8
--- /dev/null
+++ b/omagent_core/engine/http/models/correlation_ids_search_request.py
@@ -0,0 +1,123 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class CorrelationIdsSearchRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"correlation_ids": "list[str]", "workflow_names": "list[str]"}
+
+    attribute_map = {
+        "correlation_ids": "correlationIds",
+        "workflow_names": "workflowNames",
+    }
+
+    def __init__(self, correlation_ids=None, workflow_names=None):  # noqa: E501
+        """CorrelationIdsSearchRequest - a model defined in Swagger"""  # noqa: E501
+        self._correlation_ids = None
+        self._workflow_names = None
+        self.discriminator = None
+        self.correlation_ids = correlation_ids
+        self.workflow_names = workflow_names
+
+    @property
+    def correlation_ids(self):
+        """Gets the correlation_ids of this CorrelationIdsSearchRequest.  # noqa: E501
+
+
+        :return: The correlation_ids of this CorrelationIdsSearchRequest.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._correlation_ids
+
+    @correlation_ids.setter
+    def correlation_ids(self, correlation_ids):
+        """Sets the correlation_ids of this CorrelationIdsSearchRequest.
+
+
+        :param correlation_ids: The correlation_ids of this CorrelationIdsSearchRequest.  # noqa: E501
+        :type: list[str]
+        """
+        self._correlation_ids = correlation_ids
+
+    @property
+    def workflow_names(self):
+        """Gets the workflow_names of this CorrelationIdsSearchRequest.  # noqa: E501
+
+
+        :return: The workflow_names of this CorrelationIdsSearchRequest.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._workflow_names
+
+    @workflow_names.setter
+    def workflow_names(self, workflow_names):
+        """Sets the workflow_names of this CorrelationIdsSearchRequest.
+
+
+        :param workflow_names: The workflow_names of this CorrelationIdsSearchRequest.  # noqa: E501
+        :type: list[str]
+        """
+        self._workflow_names = workflow_names
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(CorrelationIdsSearchRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, CorrelationIdsSearchRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/create_or_update_application_request.py b/omagent_core/engine/http/models/create_or_update_application_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..f4d37836243e39421964e5078f23eea50b667a08
--- /dev/null
+++ b/omagent_core/engine/http/models/create_or_update_application_request.py
@@ -0,0 +1,99 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class CreateOrUpdateApplicationRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"name": "str"}
+
+    attribute_map = {"name": "name"}
+
+    def __init__(self, name=None):  # noqa: E501
+        """CreateOrUpdateApplicationRequest - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self.name = name
+
+    @property
+    def name(self):
+        """Gets the name of this CreateOrUpdateApplicationRequest.  # noqa: E501
+
+        Application's name e.g.: Payment Processors  # noqa: E501
+
+        :return: The name of this CreateOrUpdateApplicationRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this CreateOrUpdateApplicationRequest.
+
+        Application's name e.g.: Payment Processors  # noqa: E501
+
+        :param name: The name of this CreateOrUpdateApplicationRequest.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(CreateOrUpdateApplicationRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, CreateOrUpdateApplicationRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/event_handler.py b/omagent_core/engine/http/models/event_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..e434af3a90037a28664e7c7501b76602d9f85493
--- /dev/null
+++ b/omagent_core/engine/http/models/event_handler.py
@@ -0,0 +1,236 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class EventHandler(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "name": "str",
+        "event": "str",
+        "condition": "str",
+        "actions": "list[Action]",
+        "active": "bool",
+        "evaluator_type": "str",
+    }
+
+    attribute_map = {
+        "name": "name",
+        "event": "event",
+        "condition": "condition",
+        "actions": "actions",
+        "active": "active",
+        "evaluator_type": "evaluatorType",
+    }
+
+    def __init__(
+        self,
+        name=None,
+        event=None,
+        condition=None,
+        actions=None,
+        active=None,
+        evaluator_type=None,
+    ):  # noqa: E501
+        """EventHandler - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._event = None
+        self._condition = None
+        self._actions = None
+        self._active = None
+        self._evaluator_type = None
+        self.discriminator = None
+        self.name = name
+        self.event = event
+        if condition is not None:
+            self.condition = condition
+        self.actions = actions
+        if active is not None:
+            self.active = active
+        if evaluator_type is not None:
+            self.evaluator_type = evaluator_type
+
+    @property
+    def name(self):
+        """Gets the name of this EventHandler.  # noqa: E501
+
+
+        :return: The name of this EventHandler.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this EventHandler.
+
+
+        :param name: The name of this EventHandler.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def event(self):
+        """Gets the event of this EventHandler.  # noqa: E501
+
+
+        :return: The event of this EventHandler.  # noqa: E501
+        :rtype: str
+        """
+        return self._event
+
+    @event.setter
+    def event(self, event):
+        """Sets the event of this EventHandler.
+
+
+        :param event: The event of this EventHandler.  # noqa: E501
+        :type: str
+        """
+        self._event = event
+
+    @property
+    def condition(self):
+        """Gets the condition of this EventHandler.  # noqa: E501
+
+
+        :return: The condition of this EventHandler.  # noqa: E501
+        :rtype: str
+        """
+        return self._condition
+
+    @condition.setter
+    def condition(self, condition):
+        """Sets the condition of this EventHandler.
+
+
+        :param condition: The condition of this EventHandler.  # noqa: E501
+        :type: str
+        """
+
+        self._condition = condition
+
+    @property
+    def actions(self):
+        """Gets the actions of this EventHandler.  # noqa: E501
+
+
+        :return: The actions of this EventHandler.  # noqa: E501
+        :rtype: list[Action]
+        """
+        return self._actions
+
+    @actions.setter
+    def actions(self, actions):
+        """Sets the actions of this EventHandler.
+
+
+        :param actions: The actions of this EventHandler.  # noqa: E501
+        :type: list[Action]
+        """
+        self._actions = actions
+
+    @property
+    def active(self):
+        """Gets the active of this EventHandler.  # noqa: E501
+
+
+        :return: The active of this EventHandler.  # noqa: E501
+        :rtype: bool
+        """
+        return self._active
+
+    @active.setter
+    def active(self, active):
+        """Sets the active of this EventHandler.
+
+
+        :param active: The active of this EventHandler.  # noqa: E501
+        :type: bool
+        """
+
+        self._active = active
+
+    @property
+    def evaluator_type(self):
+        """Gets the evaluator_type of this EventHandler.  # noqa: E501
+
+
+        :return: The evaluator_type of this EventHandler.  # noqa: E501
+        :rtype: str
+        """
+        return self._evaluator_type
+
+    @evaluator_type.setter
+    def evaluator_type(self, evaluator_type):
+        """Sets the evaluator_type of this EventHandler.
+
+
+        :param evaluator_type: The evaluator_type of this EventHandler.  # noqa: E501
+        :type: str
+        """
+
+        self._evaluator_type = evaluator_type
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(EventHandler, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, EventHandler):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/external_storage_location.py b/omagent_core/engine/http/models/external_storage_location.py
new file mode 100644
index 0000000000000000000000000000000000000000..717429009ac152eb94dfc92f17edad164b5783f2
--- /dev/null
+++ b/omagent_core/engine/http/models/external_storage_location.py
@@ -0,0 +1,124 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class ExternalStorageLocation(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"uri": "str", "path": "str"}
+
+    attribute_map = {"uri": "uri", "path": "path"}
+
+    def __init__(self, uri=None, path=None):  # noqa: E501
+        """ExternalStorageLocation - a model defined in Swagger"""  # noqa: E501
+        self._uri = None
+        self._path = None
+        self.discriminator = None
+        if uri is not None:
+            self.uri = uri
+        if path is not None:
+            self.path = path
+
+    @property
+    def uri(self):
+        """Gets the uri of this ExternalStorageLocation.  # noqa: E501
+
+
+        :return: The uri of this ExternalStorageLocation.  # noqa: E501
+        :rtype: str
+        """
+        return self._uri
+
+    @uri.setter
+    def uri(self, uri):
+        """Sets the uri of this ExternalStorageLocation.
+
+
+        :param uri: The uri of this ExternalStorageLocation.  # noqa: E501
+        :type: str
+        """
+
+        self._uri = uri
+
+    @property
+    def path(self):
+        """Gets the path of this ExternalStorageLocation.  # noqa: E501
+
+
+        :return: The path of this ExternalStorageLocation.  # noqa: E501
+        :rtype: str
+        """
+        return self._path
+
+    @path.setter
+    def path(self, path):
+        """Sets the path of this ExternalStorageLocation.
+
+
+        :param path: The path of this ExternalStorageLocation.  # noqa: E501
+        :type: str
+        """
+
+        self._path = path
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(ExternalStorageLocation, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, ExternalStorageLocation):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/generate_token_request.py b/omagent_core/engine/http/models/generate_token_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..08e6fff5ed7867e0c3d01e9949307c9f7bef5144
--- /dev/null
+++ b/omagent_core/engine/http/models/generate_token_request.py
@@ -0,0 +1,120 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class GenerateTokenRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"key_id": "str", "key_secret": "str"}
+
+    attribute_map = {"key_id": "keyId", "key_secret": "keySecret"}
+
+    def __init__(self, key_id=None, key_secret=None):  # noqa: E501
+        """GenerateTokenRequest - a model defined in Swagger"""  # noqa: E501
+        self._key_id = None
+        self._key_secret = None
+        self.discriminator = None
+        self.key_id = key_id
+        self.key_secret = key_secret
+
+    @property
+    def key_id(self):
+        """Gets the key_id of this GenerateTokenRequest.  # noqa: E501
+
+
+        :return: The key_id of this GenerateTokenRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._key_id
+
+    @key_id.setter
+    def key_id(self, key_id):
+        """Sets the key_id of this GenerateTokenRequest.
+
+
+        :param key_id: The key_id of this GenerateTokenRequest.  # noqa: E501
+        :type: str
+        """
+        self._key_id = key_id
+
+    @property
+    def key_secret(self):
+        """Gets the key_secret of this GenerateTokenRequest.  # noqa: E501
+
+
+        :return: The key_secret of this GenerateTokenRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._key_secret
+
+    @key_secret.setter
+    def key_secret(self, key_secret):
+        """Sets the key_secret of this GenerateTokenRequest.
+
+
+        :param key_secret: The key_secret of this GenerateTokenRequest.  # noqa: E501
+        :type: str
+        """
+        self._key_secret = key_secret
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(GenerateTokenRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, GenerateTokenRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/group.py b/omagent_core/engine/http/models/group.py
new file mode 100644
index 0000000000000000000000000000000000000000..1136b48a00980fd0ff82d27bfa18470550252577
--- /dev/null
+++ b/omagent_core/engine/http/models/group.py
@@ -0,0 +1,148 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class Group(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"id": "str", "description": "str", "roles": "list[Role]"}
+
+    attribute_map = {"id": "id", "description": "description", "roles": "roles"}
+
+    def __init__(self, id=None, description=None, roles=None):  # noqa: E501
+        """Group - a model defined in Swagger"""  # noqa: E501
+        self._id = None
+        self._description = None
+        self._roles = None
+        self.discriminator = None
+        if id is not None:
+            self.id = id
+        if description is not None:
+            self.description = description
+        if roles is not None:
+            self.roles = roles
+
+    @property
+    def id(self):
+        """Gets the id of this Group.  # noqa: E501
+
+
+        :return: The id of this Group.  # noqa: E501
+        :rtype: str
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this Group.
+
+
+        :param id: The id of this Group.  # noqa: E501
+        :type: str
+        """
+
+        self._id = id
+
+    @property
+    def description(self):
+        """Gets the description of this Group.  # noqa: E501
+
+
+        :return: The description of this Group.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this Group.
+
+
+        :param description: The description of this Group.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def roles(self):
+        """Gets the roles of this Group.  # noqa: E501
+
+
+        :return: The roles of this Group.  # noqa: E501
+        :rtype: list[Role]
+        """
+        return self._roles
+
+    @roles.setter
+    def roles(self, roles):
+        """Sets the roles of this Group.
+
+
+        :param roles: The roles of this Group.  # noqa: E501
+        :type: list[Role]
+        """
+
+        self._roles = roles
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Group, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Group):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/health.py b/omagent_core/engine/http/models/health.py
new file mode 100644
index 0000000000000000000000000000000000000000..7e33d4d3a45e71372066b0645f14cee7205bf138
--- /dev/null
+++ b/omagent_core/engine/http/models/health.py
@@ -0,0 +1,156 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class Health(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "details": "dict(str, object)",
+        "error_message": "str",
+        "healthy": "bool",
+    }
+
+    attribute_map = {
+        "details": "details",
+        "error_message": "errorMessage",
+        "healthy": "healthy",
+    }
+
+    def __init__(self, details=None, error_message=None, healthy=None):  # noqa: E501
+        """Health - a model defined in Swagger"""  # noqa: E501
+        self._details = None
+        self._error_message = None
+        self._healthy = None
+        self.discriminator = None
+        if details is not None:
+            self.details = details
+        if error_message is not None:
+            self.error_message = error_message
+        if healthy is not None:
+            self.healthy = healthy
+
+    @property
+    def details(self):
+        """Gets the details of this Health.  # noqa: E501
+
+
+        :return: The details of this Health.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._details
+
+    @details.setter
+    def details(self, details):
+        """Sets the details of this Health.
+
+
+        :param details: The details of this Health.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._details = details
+
+    @property
+    def error_message(self):
+        """Gets the error_message of this Health.  # noqa: E501
+
+
+        :return: The error_message of this Health.  # noqa: E501
+        :rtype: str
+        """
+        return self._error_message
+
+    @error_message.setter
+    def error_message(self, error_message):
+        """Sets the error_message of this Health.
+
+
+        :param error_message: The error_message of this Health.  # noqa: E501
+        :type: str
+        """
+
+        self._error_message = error_message
+
+    @property
+    def healthy(self):
+        """Gets the healthy of this Health.  # noqa: E501
+
+
+        :return: The healthy of this Health.  # noqa: E501
+        :rtype: bool
+        """
+        return self._healthy
+
+    @healthy.setter
+    def healthy(self, healthy):
+        """Sets the healthy of this Health.
+
+
+        :param healthy: The healthy of this Health.  # noqa: E501
+        :type: bool
+        """
+
+        self._healthy = healthy
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Health, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Health):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/health_check_status.py b/omagent_core/engine/http/models/health_check_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..c704ef0e7d1d2162a56f686fe3d71ee9077eb41a
--- /dev/null
+++ b/omagent_core/engine/http/models/health_check_status.py
@@ -0,0 +1,158 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class HealthCheckStatus(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "health_results": "list[Health]",
+        "suppressed_health_results": "list[Health]",
+        "healthy": "bool",
+    }
+
+    attribute_map = {
+        "health_results": "healthResults",
+        "suppressed_health_results": "suppressedHealthResults",
+        "healthy": "healthy",
+    }
+
+    def __init__(
+        self, health_results=None, suppressed_health_results=None, healthy=None
+    ):  # noqa: E501
+        """HealthCheckStatus - a model defined in Swagger"""  # noqa: E501
+        self._health_results = None
+        self._suppressed_health_results = None
+        self._healthy = None
+        self.discriminator = None
+        if health_results is not None:
+            self.health_results = health_results
+        if suppressed_health_results is not None:
+            self.suppressed_health_results = suppressed_health_results
+        if healthy is not None:
+            self.healthy = healthy
+
+    @property
+    def health_results(self):
+        """Gets the health_results of this HealthCheckStatus.  # noqa: E501
+
+
+        :return: The health_results of this HealthCheckStatus.  # noqa: E501
+        :rtype: list[Health]
+        """
+        return self._health_results
+
+    @health_results.setter
+    def health_results(self, health_results):
+        """Sets the health_results of this HealthCheckStatus.
+
+
+        :param health_results: The health_results of this HealthCheckStatus.  # noqa: E501
+        :type: list[Health]
+        """
+
+        self._health_results = health_results
+
+    @property
+    def suppressed_health_results(self):
+        """Gets the suppressed_health_results of this HealthCheckStatus.  # noqa: E501
+
+
+        :return: The suppressed_health_results of this HealthCheckStatus.  # noqa: E501
+        :rtype: list[Health]
+        """
+        return self._suppressed_health_results
+
+    @suppressed_health_results.setter
+    def suppressed_health_results(self, suppressed_health_results):
+        """Sets the suppressed_health_results of this HealthCheckStatus.
+
+
+        :param suppressed_health_results: The suppressed_health_results of this HealthCheckStatus.  # noqa: E501
+        :type: list[Health]
+        """
+
+        self._suppressed_health_results = suppressed_health_results
+
+    @property
+    def healthy(self):
+        """Gets the healthy of this HealthCheckStatus.  # noqa: E501
+
+
+        :return: The healthy of this HealthCheckStatus.  # noqa: E501
+        :rtype: bool
+        """
+        return self._healthy
+
+    @healthy.setter
+    def healthy(self, healthy):
+        """Sets the healthy of this HealthCheckStatus.
+
+
+        :param healthy: The healthy of this HealthCheckStatus.  # noqa: E501
+        :type: bool
+        """
+
+        self._healthy = healthy
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(HealthCheckStatus, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, HealthCheckStatus):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/integration.py b/omagent_core/engine/http/models/integration.py
new file mode 100644
index 0000000000000000000000000000000000000000..b5ad59c7d8d512624caa565b2119d6134ec47293
--- /dev/null
+++ b/omagent_core/engine/http/models/integration.py
@@ -0,0 +1,411 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class Integration(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "category": "str",
+        "configuration": "dict(str, object)",
+        "created_by": "str",
+        "created_on": "int",
+        "description": "str",
+        "enabled": "bool",
+        "models_count": "int",
+        "name": "str",
+        "tags": "list[TagObject]",
+        "type": "str",
+        "updated_by": "str",
+        "updated_on": "int",
+    }
+
+    attribute_map = {
+        "category": "category",
+        "configuration": "configuration",
+        "created_by": "createdBy",
+        "created_on": "createdOn",
+        "description": "description",
+        "enabled": "enabled",
+        "models_count": "modelsCount",
+        "name": "name",
+        "tags": "tags",
+        "type": "type",
+        "updated_by": "updatedBy",
+        "updated_on": "updatedOn",
+    }
+
+    def __init__(
+        self,
+        category=None,
+        configuration=None,
+        created_by=None,
+        created_on=None,
+        description=None,
+        enabled=None,
+        models_count=None,
+        name=None,
+        tags=None,
+        type=None,
+        updated_by=None,
+        updated_on=None,
+    ):  # noqa: E501
+        """Integration - a model defined in Swagger"""  # noqa: E501
+        self._category = None
+        self._configuration = None
+        self._created_by = None
+        self._created_on = None
+        self._description = None
+        self._enabled = None
+        self._models_count = None
+        self._name = None
+        self._tags = None
+        self._type = None
+        self._updated_by = None
+        self._updated_on = None
+        self.discriminator = None
+        if category is not None:
+            self.category = category
+        if configuration is not None:
+            self.configuration = configuration
+        if created_by is not None:
+            self.created_by = created_by
+        if created_on is not None:
+            self.created_on = created_on
+        if description is not None:
+            self.description = description
+        if enabled is not None:
+            self.enabled = enabled
+        if models_count is not None:
+            self.models_count = models_count
+        if name is not None:
+            self.name = name
+        if tags is not None:
+            self.tags = tags
+        if type is not None:
+            self.type = type
+        if updated_by is not None:
+            self.updated_by = updated_by
+        if updated_on is not None:
+            self.updated_on = updated_on
+
+    @property
+    def category(self):
+        """Gets the category of this Integration.  # noqa: E501
+
+
+        :return: The category of this Integration.  # noqa: E501
+        :rtype: str
+        """
+        return self._category
+
+    @category.setter
+    def category(self, category):
+        """Sets the category of this Integration.
+
+
+        :param category: The category of this Integration.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["API", "AI_MODEL", "VECTOR_DB", "RELATIONAL_DB"]  # noqa: E501
+        if category not in allowed_values:
+            raise ValueError(
+                "Invalid value for `category` ({0}), must be one of {1}".format(  # noqa: E501
+                    category, allowed_values
+                )
+            )
+
+        self._category = category
+
+    @property
+    def configuration(self):
+        """Gets the configuration of this Integration.  # noqa: E501
+
+
+        :return: The configuration of this Integration.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._configuration
+
+    @configuration.setter
+    def configuration(self, configuration):
+        """Sets the configuration of this Integration.
+
+
+        :param configuration: The configuration of this Integration.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._configuration = configuration
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this Integration.  # noqa: E501
+
+
+        :return: The created_by of this Integration.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this Integration.
+
+
+        :param created_by: The created_by of this Integration.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def created_on(self):
+        """Gets the created_on of this Integration.  # noqa: E501
+
+
+        :return: The created_on of this Integration.  # noqa: E501
+        :rtype: int
+        """
+        return self._created_on
+
+    @created_on.setter
+    def created_on(self, created_on):
+        """Sets the created_on of this Integration.
+
+
+        :param created_on: The created_on of this Integration.  # noqa: E501
+        :type: int
+        """
+
+        self._created_on = created_on
+
+    @property
+    def description(self):
+        """Gets the description of this Integration.  # noqa: E501
+
+
+        :return: The description of this Integration.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this Integration.
+
+
+        :param description: The description of this Integration.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def enabled(self):
+        """Gets the enabled of this Integration.  # noqa: E501
+
+
+        :return: The enabled of this Integration.  # noqa: E501
+        :rtype: bool
+        """
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        """Sets the enabled of this Integration.
+
+
+        :param enabled: The enabled of this Integration.  # noqa: E501
+        :type: bool
+        """
+
+        self._enabled = enabled
+
+    @property
+    def models_count(self):
+        """Gets the models_count of this Integration.  # noqa: E501
+
+
+        :return: The models_count of this Integration.  # noqa: E501
+        :rtype: int
+        """
+        return self._models_count
+
+    @models_count.setter
+    def models_count(self, models_count):
+        """Sets the models_count of this Integration.
+
+
+        :param models_count: The models_count of this Integration.  # noqa: E501
+        :type: int
+        """
+
+        self._models_count = models_count
+
+    @property
+    def name(self):
+        """Gets the name of this Integration.  # noqa: E501
+
+
+        :return: The name of this Integration.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this Integration.
+
+
+        :param name: The name of this Integration.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def tags(self):
+        """Gets the tags of this Integration.  # noqa: E501
+
+
+        :return: The tags of this Integration.  # noqa: E501
+        :rtype: list[TagObject]
+        """
+        return self._tags
+
+    @tags.setter
+    def tags(self, tags):
+        """Sets the tags of this Integration.
+
+
+        :param tags: The tags of this Integration.  # noqa: E501
+        :type: list[TagObject]
+        """
+
+        self._tags = tags
+
+    @property
+    def type(self):
+        """Gets the type of this Integration.  # noqa: E501
+
+
+        :return: The type of this Integration.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this Integration.
+
+
+        :param type: The type of this Integration.  # noqa: E501
+        :type: str
+        """
+
+        self._type = type
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this Integration.  # noqa: E501
+
+
+        :return: The updated_by of this Integration.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this Integration.
+
+
+        :param updated_by: The updated_by of this Integration.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    @property
+    def updated_on(self):
+        """Gets the updated_on of this Integration.  # noqa: E501
+
+
+        :return: The updated_on of this Integration.  # noqa: E501
+        :rtype: int
+        """
+        return self._updated_on
+
+    @updated_on.setter
+    def updated_on(self, updated_on):
+        """Sets the updated_on of this Integration.
+
+
+        :param updated_on: The updated_on of this Integration.  # noqa: E501
+        :type: int
+        """
+
+        self._updated_on = updated_on
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Integration, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Integration):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/integration_api.py b/omagent_core/engine/http/models/integration_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8cfef38cfb2f6fb9ec8b38e2910863b4d032437
--- /dev/null
+++ b/omagent_core/engine/http/models/integration_api.py
@@ -0,0 +1,346 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class IntegrationApi(object):
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+
+    swagger_types = {
+        "api": "str",
+        "configuration": "dict(str, object)",
+        "created_by": "str",
+        "created_on": "int",
+        "description": "str",
+        "enabled": "bool",
+        "integration_name": "str",
+        "tags": "list[TagObject]",
+        "updated_by": "str",
+        "updated_on": "int",
+    }
+
+    attribute_map = {
+        "api": "api",
+        "configuration": "configuration",
+        "created_by": "createdBy",
+        "created_on": "createdOn",
+        "description": "description",
+        "enabled": "enabled",
+        "integration_name": "integrationName",
+        "tags": "tags",
+        "updated_by": "updatedBy",
+        "updated_on": "updatedOn",
+    }
+
+    def __init__(
+        self,
+        api=None,
+        configuration=None,
+        created_by=None,
+        created_on=None,
+        description=None,
+        enabled=None,
+        integration_name=None,
+        tags=None,
+        updated_by=None,
+        updated_on=None,
+    ):  # noqa: E501
+        """IntegrationApi - a model defined in Swagger"""  # noqa: E501
+        self._api = None
+        self._configuration = None
+        self._created_by = None
+        self._created_on = None
+        self._description = None
+        self._enabled = None
+        self._integration_name = None
+        self._tags = None
+        self._updated_by = None
+        self._updated_on = None
+        self.discriminator = None
+        if api is not None:
+            self.api = api
+        if configuration is not None:
+            self.configuration = configuration
+        if created_by is not None:
+            self.created_by = created_by
+        if created_on is not None:
+            self.created_on = created_on
+        if description is not None:
+            self.description = description
+        if enabled is not None:
+            self.enabled = enabled
+        if integration_name is not None:
+            self.integration_name = integration_name
+        if tags is not None:
+            self.tags = tags
+        if updated_by is not None:
+            self.updated_by = updated_by
+        if updated_on is not None:
+            self.updated_on = updated_on
+
+    @property
+    def api(self):
+        """Gets the api of this IntegrationApi.  # noqa: E501
+
+
+        :return: The api of this IntegrationApi.  # noqa: E501
+        :rtype: str
+        """
+        return self._api
+
+    @api.setter
+    def api(self, api):
+        """Sets the api of this IntegrationApi.
+
+
+        :param api: The api of this IntegrationApi.  # noqa: E501
+        :type: str
+        """
+
+        self._api = api
+
+    @property
+    def configuration(self):
+        """Gets the configuration of this IntegrationApi.  # noqa: E501
+
+
+        :return: The configuration of this IntegrationApi.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._configuration
+
+    @configuration.setter
+    def configuration(self, configuration):
+        """Sets the configuration of this IntegrationApi.
+
+
+        :param configuration: The configuration of this IntegrationApi.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._configuration = configuration
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this IntegrationApi.  # noqa: E501
+
+
+        :return: The created_by of this IntegrationApi.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this IntegrationApi.
+
+
+        :param created_by: The created_by of this IntegrationApi.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def created_on(self):
+        """Gets the created_on of this IntegrationApi.  # noqa: E501
+
+
+        :return: The created_on of this IntegrationApi.  # noqa: E501
+        :rtype: int
+        """
+        return self._created_on
+
+    @created_on.setter
+    def created_on(self, created_on):
+        """Sets the created_on of this IntegrationApi.
+
+
+        :param created_on: The created_on of this IntegrationApi.  # noqa: E501
+        :type: int
+        """
+
+        self._created_on = created_on
+
+    @property
+    def description(self):
+        """Gets the description of this IntegrationApi.  # noqa: E501
+
+
+        :return: The description of this IntegrationApi.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this IntegrationApi.
+
+
+        :param description: The description of this IntegrationApi.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def enabled(self):
+        """Gets the enabled of this IntegrationApi.  # noqa: E501
+
+
+        :return: The enabled of this IntegrationApi.  # noqa: E501
+        :rtype: bool
+        """
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        """Sets the enabled of this IntegrationApi.
+
+
+        :param enabled: The enabled of this IntegrationApi.  # noqa: E501
+        :type: bool
+        """
+
+        self._enabled = enabled
+
+    @property
+    def integration_name(self):
+        """Gets the integration_name of this IntegrationApi.  # noqa: E501
+
+
+        :return: The integration_name of this IntegrationApi.  # noqa: E501
+        :rtype: str
+        """
+        return self._integration_name
+
+    @integration_name.setter
+    def integration_name(self, integration_name):
+        """Sets the integration_name of this IntegrationApi.
+
+
+        :param integration_name: The integration_name of this IntegrationApi.  # noqa: E501
+        :type: str
+        """
+
+        self._integration_name = integration_name
+
+    @property
+    def tags(self):
+        """Gets the tags of this IntegrationApi.  # noqa: E501
+
+
+        :return: The tags of this IntegrationApi.  # noqa: E501
+        :rtype: list[TagObject]
+        """
+        return self._tags
+
+    @tags.setter
+    def tags(self, tags):
+        """Sets the tags of this IntegrationApi.
+
+
+        :param tags: The tags of this IntegrationApi.  # noqa: E501
+        :type: list[TagObject]
+        """
+
+        self._tags = tags
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this IntegrationApi.  # noqa: E501
+
+
+        :return: The updated_by of this IntegrationApi.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this IntegrationApi.
+
+
+        :param updated_by: The updated_by of this IntegrationApi.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    @property
+    def updated_on(self):
+        """Gets the updated_on of this IntegrationApi.  # noqa: E501
+
+
+        :return: The updated_on of this IntegrationApi.  # noqa: E501
+        :rtype: int
+        """
+        return self._updated_on
+
+    @updated_on.setter
+    def updated_on(self, updated_on):
+        """Sets the updated_on of this IntegrationApi.
+
+
+        :param updated_on: The updated_on of this IntegrationApi.  # noqa: E501
+        :type: int
+        """
+
+        self._updated_on = updated_on
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(IntegrationApi, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, IntegrationApi):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/integration_api_update.py b/omagent_core/engine/http/models/integration_api_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2a1c9fb6d64318a6fce1fad38868ae0afc6603f
--- /dev/null
+++ b/omagent_core/engine/http/models/integration_api_update.py
@@ -0,0 +1,158 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class IntegrationApiUpdate(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "configuration": "dict(str, object)",
+        "description": "str",
+        "enabled": "bool",
+    }
+
+    attribute_map = {
+        "configuration": "configuration",
+        "description": "description",
+        "enabled": "enabled",
+    }
+
+    def __init__(
+        self, configuration=None, description=None, enabled=None
+    ):  # noqa: E501
+        """IntegrationApiUpdate - a model defined in Swagger"""  # noqa: E501
+        self._configuration = None
+        self._description = None
+        self._enabled = None
+        self.discriminator = None
+        if configuration is not None:
+            self.configuration = configuration
+        if description is not None:
+            self.description = description
+        if enabled is not None:
+            self.enabled = enabled
+
+    @property
+    def configuration(self):
+        """Gets the configuration of this IntegrationApiUpdate.  # noqa: E501
+
+
+        :return: The configuration of this IntegrationApiUpdate.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._configuration
+
+    @configuration.setter
+    def configuration(self, configuration):
+        """Sets the configuration of this IntegrationApiUpdate.
+
+
+        :param configuration: The configuration of this IntegrationApiUpdate.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._configuration = configuration
+
+    @property
+    def description(self):
+        """Gets the description of this IntegrationApiUpdate.  # noqa: E501
+
+
+        :return: The description of this IntegrationApiUpdate.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this IntegrationApiUpdate.
+
+
+        :param description: The description of this IntegrationApiUpdate.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def enabled(self):
+        """Gets the enabled of this IntegrationApiUpdate.  # noqa: E501
+
+
+        :return: The enabled of this IntegrationApiUpdate.  # noqa: E501
+        :rtype: bool
+        """
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        """Sets the enabled of this IntegrationApiUpdate.
+
+
+        :param enabled: The enabled of this IntegrationApiUpdate.  # noqa: E501
+        :type: bool
+        """
+
+        self._enabled = enabled
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(IntegrationApiUpdate, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, IntegrationApiUpdate):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/integration_def.py b/omagent_core/engine/http/models/integration_def.py
new file mode 100644
index 0000000000000000000000000000000000000000..7ab624284a4cd66f1fb2f9882c7913894ddc6f5f
--- /dev/null
+++ b/omagent_core/engine/http/models/integration_def.py
@@ -0,0 +1,326 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class IntegrationDef(object):
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+
+    swagger_types = {
+        "category": "str",
+        "category_label": "str",
+        "configuration": "dict(str, object)",
+        "description": "str",
+        "enabled": "bool",
+        "icon_name": "str",
+        "name": "str",
+        "tags": "list[str]",
+        "type": "str",
+    }
+
+    attribute_map = {
+        "category": "category",
+        "category_label": "categoryLabel",
+        "configuration": "configuration",
+        "description": "description",
+        "enabled": "enabled",
+        "icon_name": "iconName",
+        "name": "name",
+        "tags": "tags",
+        "type": "type",
+    }
+
+    def __init__(
+        self,
+        category=None,
+        category_label=None,
+        configuration=None,
+        description=None,
+        enabled=None,
+        icon_name=None,
+        name=None,
+        tags=None,
+        type=None,
+    ):  # noqa: E501
+        """IntegrationDef - a model defined in Swagger"""  # noqa: E501
+        self._category = None
+        self._category_label = None
+        self._configuration = None
+        self._description = None
+        self._enabled = None
+        self._icon_name = None
+        self._name = None
+        self._tags = None
+        self._type = None
+        self.discriminator = None
+        if category is not None:
+            self.category = category
+        if category_label is not None:
+            self.category_label = category_label
+        if configuration is not None:
+            self.configuration = configuration
+        if description is not None:
+            self.description = description
+        if enabled is not None:
+            self.enabled = enabled
+        if icon_name is not None:
+            self.icon_name = icon_name
+        if name is not None:
+            self.name = name
+        if tags is not None:
+            self.tags = tags
+        if type is not None:
+            self.type = type
+
+    @property
+    def category(self):
+        """Gets the category of this IntegrationDef.  # noqa: E501
+
+
+        :return: The category of this IntegrationDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._category
+
+    @category.setter
+    def category(self, category):
+        """Sets the category of this IntegrationDef.
+
+
+        :param category: The category of this IntegrationDef.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["API", "AI_MODEL", "VECTOR_DB", "RELATIONAL_DB"]  # noqa: E501
+        if category not in allowed_values:
+            raise ValueError(
+                "Invalid value for `category` ({0}), must be one of {1}".format(  # noqa: E501
+                    category, allowed_values
+                )
+            )
+
+        self._category = category
+
+    @property
+    def category_label(self):
+        """Gets the category_label of this IntegrationDef.  # noqa: E501
+
+
+        :return: The category_label of this IntegrationDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._category_label
+
+    @category_label.setter
+    def category_label(self, category_label):
+        """Sets the category_label of this IntegrationDef.
+
+
+        :param category_label: The category_label of this IntegrationDef.  # noqa: E501
+        :type: str
+        """
+
+        self._category_label = category_label
+
+    @property
+    def configuration(self):
+        """Gets the configuration of this IntegrationDef.  # noqa: E501
+
+
+        :return: The configuration of this IntegrationDef.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._configuration
+
+    @configuration.setter
+    def configuration(self, configuration):
+        """Sets the configuration of this IntegrationDef.
+
+
+        :param configuration: The configuration of this IntegrationDef.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._configuration = configuration
+
+    @property
+    def description(self):
+        """Gets the description of this IntegrationDef.  # noqa: E501
+
+
+        :return: The description of this IntegrationDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this IntegrationDef.
+
+
+        :param description: The description of this IntegrationDef.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def enabled(self):
+        """Gets the enabled of this IntegrationDef.  # noqa: E501
+
+
+        :return: The enabled of this IntegrationDef.  # noqa: E501
+        :rtype: bool
+        """
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        """Sets the enabled of this IntegrationDef.
+
+
+        :param enabled: The enabled of this IntegrationDef.  # noqa: E501
+        :type: bool
+        """
+
+        self._enabled = enabled
+
+    @property
+    def icon_name(self):
+        """Gets the icon_name of this IntegrationDef.  # noqa: E501
+
+
+        :return: The icon_name of this IntegrationDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._icon_name
+
+    @icon_name.setter
+    def icon_name(self, icon_name):
+        """Sets the icon_name of this IntegrationDef.
+
+
+        :param icon_name: The icon_name of this IntegrationDef.  # noqa: E501
+        :type: str
+        """
+
+        self._icon_name = icon_name
+
+    @property
+    def name(self):
+        """Gets the name of this IntegrationDef.  # noqa: E501
+
+
+        :return: The name of this IntegrationDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this IntegrationDef.
+
+
+        :param name: The name of this IntegrationDef.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def tags(self):
+        """Gets the tags of this IntegrationDef.  # noqa: E501
+
+
+        :return: The tags of this IntegrationDef.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._tags
+
+    @tags.setter
+    def tags(self, tags):
+        """Sets the tags of this IntegrationDef.
+
+
+        :param tags: The tags of this IntegrationDef.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._tags = tags
+
+    @property
+    def type(self):
+        """Gets the type of this IntegrationDef.  # noqa: E501
+
+
+        :return: The type of this IntegrationDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this IntegrationDef.
+
+
+        :param type: The type of this IntegrationDef.  # noqa: E501
+        :type: str
+        """
+
+        self._type = type
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(IntegrationDef, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, IntegrationDef):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/integration_update.py b/omagent_core/engine/http/models/integration_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fc11604337917541a9d9f9b29781953e24a609a
--- /dev/null
+++ b/omagent_core/engine/http/models/integration_update.py
@@ -0,0 +1,222 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class IntegrationUpdate(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "category": "str",
+        "configuration": "dict(str, object)",
+        "description": "str",
+        "enabled": "bool",
+        "type": "str",
+    }
+
+    attribute_map = {
+        "category": "category",
+        "configuration": "configuration",
+        "description": "description",
+        "enabled": "enabled",
+        "type": "type",
+    }
+
+    def __init__(
+        self,
+        category=None,
+        configuration=None,
+        description=None,
+        enabled=None,
+        type=None,
+    ):  # noqa: E501
+        """IntegrationUpdate - a model defined in Swagger"""  # noqa: E501
+        self._category = None
+        self._configuration = None
+        self._description = None
+        self._enabled = None
+        self._type = None
+        self.discriminator = None
+        if category is not None:
+            self.category = category
+        if configuration is not None:
+            self.configuration = configuration
+        if description is not None:
+            self.description = description
+        if enabled is not None:
+            self.enabled = enabled
+        if type is not None:
+            self.type = type
+
+    @property
+    def category(self):
+        """Gets the category of this IntegrationUpdate.  # noqa: E501
+
+
+        :return: The category of this IntegrationUpdate.  # noqa: E501
+        :rtype: str
+        """
+        return self._category
+
+    @category.setter
+    def category(self, category):
+        """Sets the category of this IntegrationUpdate.
+
+
+        :param category: The category of this IntegrationUpdate.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["API", "AI_MODEL", "VECTOR_DB", "RELATIONAL_DB"]  # noqa: E501
+        if category not in allowed_values:
+            raise ValueError(
+                "Invalid value for `category` ({0}), must be one of {1}".format(  # noqa: E501
+                    category, allowed_values
+                )
+            )
+
+        self._category = category
+
+    @property
+    def configuration(self):
+        """Gets the configuration of this IntegrationUpdate.  # noqa: E501
+
+
+        :return: The configuration of this IntegrationUpdate.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._configuration
+
+    @configuration.setter
+    def configuration(self, configuration):
+        """Sets the configuration of this IntegrationUpdate.
+
+
+        :param configuration: The configuration of this IntegrationUpdate.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._configuration = configuration
+
+    @property
+    def description(self):
+        """Gets the description of this IntegrationUpdate.  # noqa: E501
+
+
+        :return: The description of this IntegrationUpdate.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this IntegrationUpdate.
+
+
+        :param description: The description of this IntegrationUpdate.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def enabled(self):
+        """Gets the enabled of this IntegrationUpdate.  # noqa: E501
+
+
+        :return: The enabled of this IntegrationUpdate.  # noqa: E501
+        :rtype: bool
+        """
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, enabled):
+        """Sets the enabled of this IntegrationUpdate.
+
+
+        :param enabled: The enabled of this IntegrationUpdate.  # noqa: E501
+        :type: bool
+        """
+
+        self._enabled = enabled
+
+    @property
+    def type(self):
+        """Gets the type of this IntegrationUpdate.  # noqa: E501
+
+
+        :return: The type of this IntegrationUpdate.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this IntegrationUpdate.
+
+
+        :param type: The type of this IntegrationUpdate.  # noqa: E501
+        :type: str
+        """
+
+        self._type = type
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(IntegrationUpdate, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, IntegrationUpdate):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/permission.py b/omagent_core/engine/http/models/permission.py
new file mode 100644
index 0000000000000000000000000000000000000000..de26142fbce8deca85a18cb19dd1f673dfa04639
--- /dev/null
+++ b/omagent_core/engine/http/models/permission.py
@@ -0,0 +1,100 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class Permission(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"name": "str"}
+
+    attribute_map = {"name": "name"}
+
+    def __init__(self, name=None):  # noqa: E501
+        """Permission - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self.discriminator = None
+        if name is not None:
+            self.name = name
+
+    @property
+    def name(self):
+        """Gets the name of this Permission.  # noqa: E501
+
+
+        :return: The name of this Permission.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this Permission.
+
+
+        :param name: The name of this Permission.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Permission, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Permission):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/poll_data.py b/omagent_core/engine/http/models/poll_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc489471caeff672bbbdb3d3ec3f2c297b66368e
--- /dev/null
+++ b/omagent_core/engine/http/models/poll_data.py
@@ -0,0 +1,184 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class PollData(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "queue_name": "str",
+        "domain": "str",
+        "worker_id": "str",
+        "last_poll_time": "int",
+    }
+
+    attribute_map = {
+        "queue_name": "queueName",
+        "domain": "domain",
+        "worker_id": "workerId",
+        "last_poll_time": "lastPollTime",
+    }
+
+    def __init__(
+        self, queue_name=None, domain=None, worker_id=None, last_poll_time=None
+    ):  # noqa: E501
+        """PollData - a model defined in Swagger"""  # noqa: E501
+        self._queue_name = None
+        self._domain = None
+        self._worker_id = None
+        self._last_poll_time = None
+        self.discriminator = None
+        if queue_name is not None:
+            self.queue_name = queue_name
+        if domain is not None:
+            self.domain = domain
+        if worker_id is not None:
+            self.worker_id = worker_id
+        if last_poll_time is not None:
+            self.last_poll_time = last_poll_time
+
+    @property
+    def queue_name(self):
+        """Gets the queue_name of this PollData.  # noqa: E501
+
+
+        :return: The queue_name of this PollData.  # noqa: E501
+        :rtype: str
+        """
+        return self._queue_name
+
+    @queue_name.setter
+    def queue_name(self, queue_name):
+        """Sets the queue_name of this PollData.
+
+
+        :param queue_name: The queue_name of this PollData.  # noqa: E501
+        :type: str
+        """
+
+        self._queue_name = queue_name
+
+    @property
+    def domain(self):
+        """Gets the domain of this PollData.  # noqa: E501
+
+
+        :return: The domain of this PollData.  # noqa: E501
+        :rtype: str
+        """
+        return self._domain
+
+    @domain.setter
+    def domain(self, domain):
+        """Sets the domain of this PollData.
+
+
+        :param domain: The domain of this PollData.  # noqa: E501
+        :type: str
+        """
+
+        self._domain = domain
+
+    @property
+    def worker_id(self):
+        """Gets the worker_id of this PollData.  # noqa: E501
+
+
+        :return: The worker_id of this PollData.  # noqa: E501
+        :rtype: str
+        """
+        return self._worker_id
+
+    @worker_id.setter
+    def worker_id(self, worker_id):
+        """Sets the worker_id of this PollData.
+
+
+        :param worker_id: The worker_id of this PollData.  # noqa: E501
+        :type: str
+        """
+
+        self._worker_id = worker_id
+
+    @property
+    def last_poll_time(self):
+        """Gets the last_poll_time of this PollData.  # noqa: E501
+
+
+        :return: The last_poll_time of this PollData.  # noqa: E501
+        :rtype: int
+        """
+        return self._last_poll_time
+
+    @last_poll_time.setter
+    def last_poll_time(self, last_poll_time):
+        """Sets the last_poll_time of this PollData.
+
+
+        :param last_poll_time: The last_poll_time of this PollData.  # noqa: E501
+        :type: int
+        """
+
+        self._last_poll_time = last_poll_time
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(PollData, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, PollData):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/prompt_template.py b/omagent_core/engine/http/models/prompt_template.py
new file mode 100644
index 0000000000000000000000000000000000000000..76fca22e4b207c77330b639c8a75b73689f87d75
--- /dev/null
+++ b/omagent_core/engine/http/models/prompt_template.py
@@ -0,0 +1,350 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class PromptTemplate(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "created_by": "str",
+        "created_on": "int",
+        "description": "str",
+        "integrations": "list[str]",
+        "name": "str",
+        "tags": "list[TagObject]",
+        "template": "str",
+        "updated_by": "str",
+        "updated_on": "int",
+        "variables": "list[str]",
+    }
+
+    attribute_map = {
+        "created_by": "createdBy",
+        "created_on": "createdOn",
+        "description": "description",
+        "integrations": "integrations",
+        "name": "name",
+        "tags": "tags",
+        "template": "template",
+        "updated_by": "updatedBy",
+        "updated_on": "updatedOn",
+        "variables": "variables",
+    }
+
+    def __init__(
+        self,
+        created_by=None,
+        created_on=None,
+        description=None,
+        integrations=None,
+        name=None,
+        tags=None,
+        template=None,
+        updated_by=None,
+        updated_on=None,
+        variables=None,
+    ):  # noqa: E501
+        """PromptTemplate - a model defined in Swagger"""  # noqa: E501
+        self._created_by = None
+        self._created_on = None
+        self._description = None
+        self._integrations = None
+        self._name = None
+        self._tags = None
+        self._template = None
+        self._updated_by = None
+        self._updated_on = None
+        self._variables = None
+        self.discriminator = None
+        if created_by is not None:
+            self.created_by = created_by
+        if created_on is not None:
+            self.created_on = created_on
+        if description is not None:
+            self.description = description
+        if integrations is not None:
+            self.integrations = integrations
+        if name is not None:
+            self.name = name
+        if tags is not None:
+            self.tags = tags
+        if template is not None:
+            self.template = template
+        if updated_by is not None:
+            self.updated_by = updated_by
+        if updated_on is not None:
+            self.updated_on = updated_on
+        if variables is not None:
+            self.variables = variables
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this PromptTemplate.  # noqa: E501
+
+
+        :return: The created_by of this PromptTemplate.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this PromptTemplate.
+
+
+        :param created_by: The created_by of this PromptTemplate.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def created_on(self):
+        """Gets the created_on of this PromptTemplate.  # noqa: E501
+
+
+        :return: The created_on of this PromptTemplate.  # noqa: E501
+        :rtype: int
+        """
+        return self._created_on
+
+    @created_on.setter
+    def created_on(self, created_on):
+        """Sets the created_on of this PromptTemplate.
+
+
+        :param created_on: The created_on of this PromptTemplate.  # noqa: E501
+        :type: int
+        """
+
+        self._created_on = created_on
+
+    @property
+    def description(self):
+        """Gets the description of this PromptTemplate.  # noqa: E501
+
+
+        :return: The description of this PromptTemplate.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this PromptTemplate.
+
+
+        :param description: The description of this PromptTemplate.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def integrations(self):
+        """Gets the integrations of this PromptTemplate.  # noqa: E501
+
+
+        :return: The integrations of this PromptTemplate.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._integrations
+
+    @integrations.setter
+    def integrations(self, integrations):
+        """Sets the integrations of this PromptTemplate.
+
+
+        :param integrations: The integrations of this PromptTemplate.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._integrations = integrations
+
+    @property
+    def name(self):
+        """Gets the name of this PromptTemplate.  # noqa: E501
+
+
+        :return: The name of this PromptTemplate.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this PromptTemplate.
+
+
+        :param name: The name of this PromptTemplate.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def tags(self):
+        """Gets the tags of this PromptTemplate.  # noqa: E501
+
+
+        :return: The tags of this PromptTemplate.  # noqa: E501
+        :rtype: list[TagObject]
+        """
+        return self._tags
+
+    @tags.setter
+    def tags(self, tags):
+        """Sets the tags of this PromptTemplate.
+
+
+        :param tags: The tags of this PromptTemplate.  # noqa: E501
+        :type: list[TagObject]
+        """
+
+        self._tags = tags
+
+    @property
+    def template(self):
+        """Gets the template of this PromptTemplate.  # noqa: E501
+
+
+        :return: The template of this PromptTemplate.  # noqa: E501
+        :rtype: str
+        """
+        return self._template
+
+    @template.setter
+    def template(self, template):
+        """Sets the template of this PromptTemplate.
+
+
+        :param template: The template of this PromptTemplate.  # noqa: E501
+        :type: str
+        """
+
+        self._template = template
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this PromptTemplate.  # noqa: E501
+
+
+        :return: The updated_by of this PromptTemplate.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this PromptTemplate.
+
+
+        :param updated_by: The updated_by of this PromptTemplate.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    @property
+    def updated_on(self):
+        """Gets the updated_on of this PromptTemplate.  # noqa: E501
+
+
+        :return: The updated_on of this PromptTemplate.  # noqa: E501
+        :rtype: int
+        """
+        return self._updated_on
+
+    @updated_on.setter
+    def updated_on(self, updated_on):
+        """Sets the updated_on of this PromptTemplate.
+
+
+        :param updated_on: The updated_on of this PromptTemplate.  # noqa: E501
+        :type: int
+        """
+
+        self._updated_on = updated_on
+
+    @property
+    def variables(self):
+        """Gets the variables of this PromptTemplate.  # noqa: E501
+
+
+        :return: The variables of this PromptTemplate.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._variables
+
+    @variables.setter
+    def variables(self, variables):
+        """Sets the variables of this PromptTemplate.
+
+
+        :param variables: The variables of this PromptTemplate.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._variables = variables
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(PromptTemplate, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, PromptTemplate):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/prompt_test_request.py b/omagent_core/engine/http/models/prompt_test_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..345f0672b6b029d0b24c265b4cb785417e3b72b7
--- /dev/null
+++ b/omagent_core/engine/http/models/prompt_test_request.py
@@ -0,0 +1,269 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class PromptTemplateTestRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "llm_provider": "str",
+        "model": "str",
+        "prompt": "str",
+        "prompt_variables": "dict(str, object)",
+        "stop_words": "list[str]",
+        "temperature": "float",
+        "top_p": "float",
+    }
+
+    attribute_map = {
+        "llm_provider": "llmProvider",
+        "model": "model",
+        "prompt": "prompt",
+        "prompt_variables": "promptVariables",
+        "stop_words": "stopWords",
+        "temperature": "temperature",
+        "top_p": "topP",
+    }
+
+    def __init__(
+        self,
+        llm_provider=None,
+        model=None,
+        prompt=None,
+        prompt_variables=None,
+        stop_words=None,
+        temperature=None,
+        top_p=None,
+    ):  # noqa: E501
+        """PromptTemplateTestRequest - a model defined in Swagger"""  # noqa: E501
+        self._llm_provider = None
+        self._model = None
+        self._prompt = None
+        self._prompt_variables = None
+        self._stop_words = None
+        self._temperature = None
+        self._top_p = None
+        self.discriminator = None
+        if llm_provider is not None:
+            self.llm_provider = llm_provider
+        if model is not None:
+            self.model = model
+        if prompt is not None:
+            self.prompt = prompt
+        if prompt_variables is not None:
+            self.prompt_variables = prompt_variables
+        if stop_words is not None:
+            self.stop_words = stop_words
+        if temperature is not None:
+            self.temperature = temperature
+        if top_p is not None:
+            self.top_p = top_p
+
+    @property
+    def llm_provider(self):
+        """Gets the llm_provider of this PromptTemplateTestRequest.  # noqa: E501
+
+
+        :return: The llm_provider of this PromptTemplateTestRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._llm_provider
+
+    @llm_provider.setter
+    def llm_provider(self, llm_provider):
+        """Sets the llm_provider of this PromptTemplateTestRequest.
+
+
+        :param llm_provider: The llm_provider of this PromptTemplateTestRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._llm_provider = llm_provider
+
+    @property
+    def model(self):
+        """Gets the model of this PromptTemplateTestRequest.  # noqa: E501
+
+
+        :return: The model of this PromptTemplateTestRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._model
+
+    @model.setter
+    def model(self, model):
+        """Sets the model of this PromptTemplateTestRequest.
+
+
+        :param model: The model of this PromptTemplateTestRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._model = model
+
+    @property
+    def prompt(self):
+        """Gets the prompt of this PromptTemplateTestRequest.  # noqa: E501
+
+
+        :return: The prompt of this PromptTemplateTestRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._prompt
+
+    @prompt.setter
+    def prompt(self, prompt):
+        """Sets the prompt of this PromptTemplateTestRequest.
+
+
+        :param prompt: The prompt of this PromptTemplateTestRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._prompt = prompt
+
+    @property
+    def prompt_variables(self):
+        """Gets the prompt_variables of this PromptTemplateTestRequest.  # noqa: E501
+
+
+        :return: The prompt_variables of this PromptTemplateTestRequest.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._prompt_variables
+
+    @prompt_variables.setter
+    def prompt_variables(self, prompt_variables):
+        """Sets the prompt_variables of this PromptTemplateTestRequest.
+
+
+        :param prompt_variables: The prompt_variables of this PromptTemplateTestRequest.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._prompt_variables = prompt_variables
+
+    @property
+    def stop_words(self):
+        """Gets the stop_words of this PromptTemplateTestRequest.  # noqa: E501
+
+
+        :return: The stop_words of this PromptTemplateTestRequest.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._stop_words
+
+    @stop_words.setter
+    def stop_words(self, stop_words):
+        """Sets the stop_words of this PromptTemplateTestRequest.
+
+
+        :param stop_words: The stop_words of this PromptTemplateTestRequest.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._stop_words = stop_words
+
+    @property
+    def temperature(self):
+        """Gets the temperature of this PromptTemplateTestRequest.  # noqa: E501
+
+
+        :return: The temperature of this PromptTemplateTestRequest.  # noqa: E501
+        :rtype: float
+        """
+        return self._temperature
+
+    @temperature.setter
+    def temperature(self, temperature):
+        """Sets the temperature of this PromptTemplateTestRequest.
+
+
+        :param temperature: The temperature of this PromptTemplateTestRequest.  # noqa: E501
+        :type: float
+        """
+
+        self._temperature = temperature
+
+    @property
+    def top_p(self):
+        """Gets the top_p of this PromptTemplateTestRequest.  # noqa: E501
+
+
+        :return: The top_p of this PromptTemplateTestRequest.  # noqa: E501
+        :rtype: float
+        """
+        return self._top_p
+
+    @top_p.setter
+    def top_p(self, top_p):
+        """Sets the top_p of this PromptTemplateTestRequest.
+
+
+        :param top_p: The top_p of this PromptTemplateTestRequest.  # noqa: E501
+        :type: float
+        """
+
+        self._top_p = top_p
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(PromptTemplateTestRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, PromptTemplateTestRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/rate_limit.py b/omagent_core/engine/http/models/rate_limit.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba2e19c99f9ee82cbaefb5572e37af79be58c421
--- /dev/null
+++ b/omagent_core/engine/http/models/rate_limit.py
@@ -0,0 +1,127 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class RateLimit(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"tag": "str", "concurrent_execution_limit": "int"}
+
+    attribute_map = {
+        "tag": "tag",
+        "concurrent_execution_limit": "concurrentExecutionLimit",
+    }
+
+    def __init__(self, tag=None, concurrent_execution_limit=None):  # noqa: E501
+        """RateLimit - a model defined in Swagger"""  # noqa: E501
+        self._tag = None
+        self._concurrent_execution_limit = None
+        self.discriminator = None
+        if tag is not None:
+            self.tag = tag
+        if concurrent_execution_limit is not None:
+            self.concurrent_execution_limit = concurrent_execution_limit
+
+    @property
+    def tag(self):
+        """Gets the tag of this RateLimit.  # noqa: E501
+
+
+        :return: The tag of this RateLimit.  # noqa: E501
+        :rtype: str
+        """
+        return self._tag
+
+    @tag.setter
+    def tag(self, tag):
+        """Sets the tag of this RateLimit.
+
+
+        :param tag: The tag of this RateLimit.  # noqa: E501
+        :type: str
+        """
+
+        self._tag = tag
+
+    @property
+    def concurrent_execution_limit(self):
+        """Gets the concurrent_execution_limit of this RateLimit.  # noqa: E501
+
+
+        :return: The concurrent_execution_limit of this RateLimit.  # noqa: E501
+        :rtype: int
+        """
+        return self._concurrent_execution_limit
+
+    @concurrent_execution_limit.setter
+    def concurrent_execution_limit(self, concurrent_execution_limit):
+        """Sets the concurrent_execution_limit of this RateLimit.
+
+
+        :param concurrent_execution_limit: The concurrent_execution_limit of this RateLimit.  # noqa: E501
+        :type: int
+        """
+
+        self._concurrent_execution_limit = concurrent_execution_limit
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(RateLimit, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, RateLimit):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/rerun_workflow_request.py b/omagent_core/engine/http/models/rerun_workflow_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d33277c1cbadef582c8687cefd11b30bc3f7a5a
--- /dev/null
+++ b/omagent_core/engine/http/models/rerun_workflow_request.py
@@ -0,0 +1,215 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class RerunWorkflowRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "re_run_from_workflow_id": "str",
+        "workflow_input": "dict(str, object)",
+        "re_run_from_task_id": "str",
+        "task_input": "dict(str, object)",
+        "correlation_id": "str",
+    }
+
+    attribute_map = {
+        "re_run_from_workflow_id": "reRunFromWorkflowId",
+        "workflow_input": "workflowInput",
+        "re_run_from_task_id": "reRunFromTaskId",
+        "task_input": "taskInput",
+        "correlation_id": "correlationId",
+    }
+
+    def __init__(
+        self,
+        re_run_from_workflow_id=None,
+        workflow_input=None,
+        re_run_from_task_id=None,
+        task_input=None,
+        correlation_id=None,
+    ):  # noqa: E501
+        """RerunWorkflowRequest - a model defined in Swagger"""  # noqa: E501
+        self._re_run_from_workflow_id = None
+        self._workflow_input = None
+        self._re_run_from_task_id = None
+        self._task_input = None
+        self._correlation_id = None
+        self.discriminator = None
+        if re_run_from_workflow_id is not None:
+            self.re_run_from_workflow_id = re_run_from_workflow_id
+        if workflow_input is not None:
+            self.workflow_input = workflow_input
+        if re_run_from_task_id is not None:
+            self.re_run_from_task_id = re_run_from_task_id
+        if task_input is not None:
+            self.task_input = task_input
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+
+    @property
+    def re_run_from_workflow_id(self):
+        """Gets the re_run_from_workflow_id of this RerunWorkflowRequest.  # noqa: E501
+
+
+        :return: The re_run_from_workflow_id of this RerunWorkflowRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._re_run_from_workflow_id
+
+    @re_run_from_workflow_id.setter
+    def re_run_from_workflow_id(self, re_run_from_workflow_id):
+        """Sets the re_run_from_workflow_id of this RerunWorkflowRequest.
+
+
+        :param re_run_from_workflow_id: The re_run_from_workflow_id of this RerunWorkflowRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._re_run_from_workflow_id = re_run_from_workflow_id
+
+    @property
+    def workflow_input(self):
+        """Gets the workflow_input of this RerunWorkflowRequest.  # noqa: E501
+
+
+        :return: The workflow_input of this RerunWorkflowRequest.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._workflow_input
+
+    @workflow_input.setter
+    def workflow_input(self, workflow_input):
+        """Sets the workflow_input of this RerunWorkflowRequest.
+
+
+        :param workflow_input: The workflow_input of this RerunWorkflowRequest.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._workflow_input = workflow_input
+
+    @property
+    def re_run_from_task_id(self):
+        """Gets the re_run_from_task_id of this RerunWorkflowRequest.  # noqa: E501
+
+
+        :return: The re_run_from_task_id of this RerunWorkflowRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._re_run_from_task_id
+
+    @re_run_from_task_id.setter
+    def re_run_from_task_id(self, re_run_from_task_id):
+        """Sets the re_run_from_task_id of this RerunWorkflowRequest.
+
+
+        :param re_run_from_task_id: The re_run_from_task_id of this RerunWorkflowRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._re_run_from_task_id = re_run_from_task_id
+
+    @property
+    def task_input(self):
+        """Gets the task_input of this RerunWorkflowRequest.  # noqa: E501
+
+
+        :return: The task_input of this RerunWorkflowRequest.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._task_input
+
+    @task_input.setter
+    def task_input(self, task_input):
+        """Sets the task_input of this RerunWorkflowRequest.
+
+
+        :param task_input: The task_input of this RerunWorkflowRequest.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._task_input = task_input
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this RerunWorkflowRequest.  # noqa: E501
+
+
+        :return: The correlation_id of this RerunWorkflowRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this RerunWorkflowRequest.
+
+
+        :param correlation_id: The correlation_id of this RerunWorkflowRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(RerunWorkflowRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, RerunWorkflowRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/response.py b/omagent_core/engine/http/models/response.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf241fd27e62191cc668164928335232d0131bdc
--- /dev/null
+++ b/omagent_core/engine/http/models/response.py
@@ -0,0 +1,76 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class Response(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {}
+
+    attribute_map = {}
+
+    def __init__(self):  # noqa: E501
+        """Response - a model defined in Swagger"""  # noqa: E501
+        self.discriminator = None
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Response, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Response):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/role.py b/omagent_core/engine/http/models/role.py
new file mode 100644
index 0000000000000000000000000000000000000000..662cd645e6286d73ecb0c57951f9e38806cc4be6
--- /dev/null
+++ b/omagent_core/engine/http/models/role.py
@@ -0,0 +1,124 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class Role(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"name": "str", "permissions": "list[Permission]"}
+
+    attribute_map = {"name": "name", "permissions": "permissions"}
+
+    def __init__(self, name=None, permissions=None):  # noqa: E501
+        """Role - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._permissions = None
+        self.discriminator = None
+        if name is not None:
+            self.name = name
+        if permissions is not None:
+            self.permissions = permissions
+
+    @property
+    def name(self):
+        """Gets the name of this Role.  # noqa: E501
+
+
+        :return: The name of this Role.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this Role.
+
+
+        :param name: The name of this Role.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def permissions(self):
+        """Gets the permissions of this Role.  # noqa: E501
+
+
+        :return: The permissions of this Role.  # noqa: E501
+        :rtype: list[Permission]
+        """
+        return self._permissions
+
+    @permissions.setter
+    def permissions(self, permissions):
+        """Sets the permissions of this Role.
+
+
+        :param permissions: The permissions of this Role.  # noqa: E501
+        :type: list[Permission]
+        """
+
+        self._permissions = permissions
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Role, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Role):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/save_schedule_request.py b/omagent_core/engine/http/models/save_schedule_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..e6386791b7dceddf7d06bc240a9374e2be6cbe0a
--- /dev/null
+++ b/omagent_core/engine/http/models/save_schedule_request.py
@@ -0,0 +1,319 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SaveScheduleRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "name": "str",
+        "cron_expression": "str",
+        "run_catchup_schedule_instances": "bool",
+        "paused": "bool",
+        "start_workflow_request": "StartWorkflowRequest",
+        "created_by": "str",
+        "updated_by": "str",
+        "schedule_start_time": "int",
+        "schedule_end_time": "int",
+    }
+
+    attribute_map = {
+        "name": "name",
+        "cron_expression": "cronExpression",
+        "run_catchup_schedule_instances": "runCatchupScheduleInstances",
+        "paused": "paused",
+        "start_workflow_request": "startWorkflowRequest",
+        "created_by": "createdBy",
+        "updated_by": "updatedBy",
+        "schedule_start_time": "scheduleStartTime",
+        "schedule_end_time": "scheduleEndTime",
+    }
+
+    def __init__(
+        self,
+        name=None,
+        cron_expression=None,
+        run_catchup_schedule_instances=None,
+        paused=None,
+        start_workflow_request=None,
+        created_by=None,
+        updated_by=None,
+        schedule_start_time=None,
+        schedule_end_time=None,
+    ):  # noqa: E501
+        """SaveScheduleRequest - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._cron_expression = None
+        self._run_catchup_schedule_instances = None
+        self._paused = None
+        self._start_workflow_request = None
+        self._created_by = None
+        self._updated_by = None
+        self._schedule_start_time = None
+        self._schedule_end_time = None
+        self.discriminator = None
+        self.name = name
+        self.cron_expression = cron_expression
+        if run_catchup_schedule_instances is not None:
+            self.run_catchup_schedule_instances = run_catchup_schedule_instances
+        if paused is not None:
+            self.paused = paused
+        if start_workflow_request is not None:
+            self.start_workflow_request = start_workflow_request
+        if created_by is not None:
+            self.created_by = created_by
+        if updated_by is not None:
+            self.updated_by = updated_by
+        if schedule_start_time is not None:
+            self.schedule_start_time = schedule_start_time
+        if schedule_end_time is not None:
+            self.schedule_end_time = schedule_end_time
+
+    @property
+    def name(self):
+        """Gets the name of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The name of this SaveScheduleRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this SaveScheduleRequest.
+
+
+        :param name: The name of this SaveScheduleRequest.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def cron_expression(self):
+        """Gets the cron_expression of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The cron_expression of this SaveScheduleRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._cron_expression
+
+    @cron_expression.setter
+    def cron_expression(self, cron_expression):
+        """Sets the cron_expression of this SaveScheduleRequest.
+
+
+        :param cron_expression: The cron_expression of this SaveScheduleRequest.  # noqa: E501
+        :type: str
+        """
+        self._cron_expression = cron_expression
+
+    @property
+    def run_catchup_schedule_instances(self):
+        """Gets the run_catchup_schedule_instances of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The run_catchup_schedule_instances of this SaveScheduleRequest.  # noqa: E501
+        :rtype: bool
+        """
+        return self._run_catchup_schedule_instances
+
+    @run_catchup_schedule_instances.setter
+    def run_catchup_schedule_instances(self, run_catchup_schedule_instances):
+        """Sets the run_catchup_schedule_instances of this SaveScheduleRequest.
+
+
+        :param run_catchup_schedule_instances: The run_catchup_schedule_instances of this SaveScheduleRequest.  # noqa: E501
+        :type: bool
+        """
+
+        self._run_catchup_schedule_instances = run_catchup_schedule_instances
+
+    @property
+    def paused(self):
+        """Gets the paused of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The paused of this SaveScheduleRequest.  # noqa: E501
+        :rtype: bool
+        """
+        return self._paused
+
+    @paused.setter
+    def paused(self, paused):
+        """Sets the paused of this SaveScheduleRequest.
+
+
+        :param paused: The paused of this SaveScheduleRequest.  # noqa: E501
+        :type: bool
+        """
+
+        self._paused = paused
+
+    @property
+    def start_workflow_request(self):
+        """Gets the start_workflow_request of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The start_workflow_request of this SaveScheduleRequest.  # noqa: E501
+        :rtype: StartWorkflowRequest
+        """
+        return self._start_workflow_request
+
+    @start_workflow_request.setter
+    def start_workflow_request(self, start_workflow_request):
+        """Sets the start_workflow_request of this SaveScheduleRequest.
+
+
+        :param start_workflow_request: The start_workflow_request of this SaveScheduleRequest.  # noqa: E501
+        :type: StartWorkflowRequest
+        """
+
+        self._start_workflow_request = start_workflow_request
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The created_by of this SaveScheduleRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this SaveScheduleRequest.
+
+
+        :param created_by: The created_by of this SaveScheduleRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The updated_by of this SaveScheduleRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this SaveScheduleRequest.
+
+
+        :param updated_by: The updated_by of this SaveScheduleRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    @property
+    def schedule_start_time(self):
+        """Gets the schedule_start_time of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The schedule_start_time of this SaveScheduleRequest.  # noqa: E501
+        :rtype: int
+        """
+        return self._schedule_start_time
+
+    @schedule_start_time.setter
+    def schedule_start_time(self, schedule_start_time):
+        """Sets the schedule_start_time of this SaveScheduleRequest.
+
+
+        :param schedule_start_time: The schedule_start_time of this SaveScheduleRequest.  # noqa: E501
+        :type: int
+        """
+
+        self._schedule_start_time = schedule_start_time
+
+    @property
+    def schedule_end_time(self):
+        """Gets the schedule_end_time of this SaveScheduleRequest.  # noqa: E501
+
+
+        :return: The schedule_end_time of this SaveScheduleRequest.  # noqa: E501
+        :rtype: int
+        """
+        return self._schedule_end_time
+
+    @schedule_end_time.setter
+    def schedule_end_time(self, schedule_end_time):
+        """Sets the schedule_end_time of this SaveScheduleRequest.
+
+
+        :param schedule_end_time: The schedule_end_time of this SaveScheduleRequest.  # noqa: E501
+        :type: int
+        """
+
+        self._schedule_end_time = schedule_end_time
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SaveScheduleRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SaveScheduleRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/scrollable_search_result_workflow_summary.py b/omagent_core/engine/http/models/scrollable_search_result_workflow_summary.py
new file mode 100644
index 0000000000000000000000000000000000000000..5d0ca4ddc9d88ca94c367c4193bd7ce59b48e231
--- /dev/null
+++ b/omagent_core/engine/http/models/scrollable_search_result_workflow_summary.py
@@ -0,0 +1,124 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class ScrollableSearchResultWorkflowSummary(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"results": "list[WorkflowSummary]", "query_id": "str"}
+
+    attribute_map = {"results": "results", "query_id": "queryId"}
+
+    def __init__(self, results=None, query_id=None):  # noqa: E501
+        """ScrollableSearchResultWorkflowSummary - a model defined in Swagger"""  # noqa: E501
+        self._results = None
+        self._query_id = None
+        self.discriminator = None
+        if results is not None:
+            self.results = results
+        if query_id is not None:
+            self.query_id = query_id
+
+    @property
+    def results(self):
+        """Gets the results of this ScrollableSearchResultWorkflowSummary.  # noqa: E501
+
+
+        :return: The results of this ScrollableSearchResultWorkflowSummary.  # noqa: E501
+        :rtype: list[WorkflowSummary]
+        """
+        return self._results
+
+    @results.setter
+    def results(self, results):
+        """Sets the results of this ScrollableSearchResultWorkflowSummary.
+
+
+        :param results: The results of this ScrollableSearchResultWorkflowSummary.  # noqa: E501
+        :type: list[WorkflowSummary]
+        """
+
+        self._results = results
+
+    @property
+    def query_id(self):
+        """Gets the query_id of this ScrollableSearchResultWorkflowSummary.  # noqa: E501
+
+
+        :return: The query_id of this ScrollableSearchResultWorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._query_id
+
+    @query_id.setter
+    def query_id(self, query_id):
+        """Sets the query_id of this ScrollableSearchResultWorkflowSummary.
+
+
+        :param query_id: The query_id of this ScrollableSearchResultWorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._query_id = query_id
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(ScrollableSearchResultWorkflowSummary, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, ScrollableSearchResultWorkflowSummary):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/search_result_task.py b/omagent_core/engine/http/models/search_result_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..1ddb908916ce5b93e0fe92cb3fd5a1a298363b2a
--- /dev/null
+++ b/omagent_core/engine/http/models/search_result_task.py
@@ -0,0 +1,124 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SearchResultTask(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"total_hits": "int", "results": "list[Task]"}
+
+    attribute_map = {"total_hits": "totalHits", "results": "results"}
+
+    def __init__(self, total_hits=None, results=None):  # noqa: E501
+        """SearchResultTask - a model defined in Swagger"""  # noqa: E501
+        self._total_hits = None
+        self._results = None
+        self.discriminator = None
+        if total_hits is not None:
+            self.total_hits = total_hits
+        if results is not None:
+            self.results = results
+
+    @property
+    def total_hits(self):
+        """Gets the total_hits of this SearchResultTask.  # noqa: E501
+
+
+        :return: The total_hits of this SearchResultTask.  # noqa: E501
+        :rtype: int
+        """
+        return self._total_hits
+
+    @total_hits.setter
+    def total_hits(self, total_hits):
+        """Sets the total_hits of this SearchResultTask.
+
+
+        :param total_hits: The total_hits of this SearchResultTask.  # noqa: E501
+        :type: int
+        """
+
+        self._total_hits = total_hits
+
+    @property
+    def results(self):
+        """Gets the results of this SearchResultTask.  # noqa: E501
+
+
+        :return: The results of this SearchResultTask.  # noqa: E501
+        :rtype: list[Task]
+        """
+        return self._results
+
+    @results.setter
+    def results(self, results):
+        """Sets the results of this SearchResultTask.
+
+
+        :param results: The results of this SearchResultTask.  # noqa: E501
+        :type: list[Task]
+        """
+
+        self._results = results
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SearchResultTask, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SearchResultTask):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/search_result_task_summary.py b/omagent_core/engine/http/models/search_result_task_summary.py
new file mode 100644
index 0000000000000000000000000000000000000000..de86e22d2ce3d111ddfe6fc31a56053e3b343cc7
--- /dev/null
+++ b/omagent_core/engine/http/models/search_result_task_summary.py
@@ -0,0 +1,124 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SearchResultTaskSummary(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"total_hits": "int", "results": "list[TaskSummary]"}
+
+    attribute_map = {"total_hits": "totalHits", "results": "results"}
+
+    def __init__(self, total_hits=None, results=None):  # noqa: E501
+        """SearchResultTaskSummary - a model defined in Swagger"""  # noqa: E501
+        self._total_hits = None
+        self._results = None
+        self.discriminator = None
+        if total_hits is not None:
+            self.total_hits = total_hits
+        if results is not None:
+            self.results = results
+
+    @property
+    def total_hits(self):
+        """Gets the total_hits of this SearchResultTaskSummary.  # noqa: E501
+
+
+        :return: The total_hits of this SearchResultTaskSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._total_hits
+
+    @total_hits.setter
+    def total_hits(self, total_hits):
+        """Sets the total_hits of this SearchResultTaskSummary.
+
+
+        :param total_hits: The total_hits of this SearchResultTaskSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._total_hits = total_hits
+
+    @property
+    def results(self):
+        """Gets the results of this SearchResultTaskSummary.  # noqa: E501
+
+
+        :return: The results of this SearchResultTaskSummary.  # noqa: E501
+        :rtype: list[TaskSummary]
+        """
+        return self._results
+
+    @results.setter
+    def results(self, results):
+        """Sets the results of this SearchResultTaskSummary.
+
+
+        :param results: The results of this SearchResultTaskSummary.  # noqa: E501
+        :type: list[TaskSummary]
+        """
+
+        self._results = results
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SearchResultTaskSummary, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SearchResultTaskSummary):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/search_result_workflow.py b/omagent_core/engine/http/models/search_result_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..77c92d5eea00e0771f47e68787ef8d480f2b7f27
--- /dev/null
+++ b/omagent_core/engine/http/models/search_result_workflow.py
@@ -0,0 +1,124 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SearchResultWorkflow(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"total_hits": "int", "results": "list[Workflow]"}
+
+    attribute_map = {"total_hits": "totalHits", "results": "results"}
+
+    def __init__(self, total_hits=None, results=None):  # noqa: E501
+        """SearchResultWorkflow - a model defined in Swagger"""  # noqa: E501
+        self._total_hits = None
+        self._results = None
+        self.discriminator = None
+        if total_hits is not None:
+            self.total_hits = total_hits
+        if results is not None:
+            self.results = results
+
+    @property
+    def total_hits(self):
+        """Gets the total_hits of this SearchResultWorkflow.  # noqa: E501
+
+
+        :return: The total_hits of this SearchResultWorkflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._total_hits
+
+    @total_hits.setter
+    def total_hits(self, total_hits):
+        """Sets the total_hits of this SearchResultWorkflow.
+
+
+        :param total_hits: The total_hits of this SearchResultWorkflow.  # noqa: E501
+        :type: int
+        """
+
+        self._total_hits = total_hits
+
+    @property
+    def results(self):
+        """Gets the results of this SearchResultWorkflow.  # noqa: E501
+
+
+        :return: The results of this SearchResultWorkflow.  # noqa: E501
+        :rtype: list[Workflow]
+        """
+        return self._results
+
+    @results.setter
+    def results(self, results):
+        """Sets the results of this SearchResultWorkflow.
+
+
+        :param results: The results of this SearchResultWorkflow.  # noqa: E501
+        :type: list[Workflow]
+        """
+
+        self._results = results
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SearchResultWorkflow, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SearchResultWorkflow):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/search_result_workflow_schedule_execution_model.py b/omagent_core/engine/http/models/search_result_workflow_schedule_execution_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1a34b38e62f9e694ec982ca05388a22dea9bf9e
--- /dev/null
+++ b/omagent_core/engine/http/models/search_result_workflow_schedule_execution_model.py
@@ -0,0 +1,127 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SearchResultWorkflowScheduleExecutionModel(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "total_hits": "int",
+        "results": "list[WorkflowScheduleExecutionModel]",
+    }
+
+    attribute_map = {"total_hits": "totalHits", "results": "results"}
+
+    def __init__(self, total_hits=None, results=None):  # noqa: E501
+        """SearchResultWorkflowScheduleExecutionModel - a model defined in Swagger"""  # noqa: E501
+        self._total_hits = None
+        self._results = None
+        self.discriminator = None
+        if total_hits is not None:
+            self.total_hits = total_hits
+        if results is not None:
+            self.results = results
+
+    @property
+    def total_hits(self):
+        """Gets the total_hits of this SearchResultWorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The total_hits of this SearchResultWorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: int
+        """
+        return self._total_hits
+
+    @total_hits.setter
+    def total_hits(self, total_hits):
+        """Sets the total_hits of this SearchResultWorkflowScheduleExecutionModel.
+
+
+        :param total_hits: The total_hits of this SearchResultWorkflowScheduleExecutionModel.  # noqa: E501
+        :type: int
+        """
+
+        self._total_hits = total_hits
+
+    @property
+    def results(self):
+        """Gets the results of this SearchResultWorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The results of this SearchResultWorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: list[WorkflowScheduleExecutionModel]
+        """
+        return self._results
+
+    @results.setter
+    def results(self, results):
+        """Sets the results of this SearchResultWorkflowScheduleExecutionModel.
+
+
+        :param results: The results of this SearchResultWorkflowScheduleExecutionModel.  # noqa: E501
+        :type: list[WorkflowScheduleExecutionModel]
+        """
+
+        self._results = results
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SearchResultWorkflowScheduleExecutionModel, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SearchResultWorkflowScheduleExecutionModel):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/search_result_workflow_summary.py b/omagent_core/engine/http/models/search_result_workflow_summary.py
new file mode 100644
index 0000000000000000000000000000000000000000..12e330ba6a5e9eea92507dcdb16913c87e43a941
--- /dev/null
+++ b/omagent_core/engine/http/models/search_result_workflow_summary.py
@@ -0,0 +1,124 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SearchResultWorkflowSummary(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"total_hits": "int", "results": "list[WorkflowSummary]"}
+
+    attribute_map = {"total_hits": "totalHits", "results": "results"}
+
+    def __init__(self, total_hits=None, results=None):  # noqa: E501
+        """SearchResultWorkflowSummary - a model defined in Swagger"""  # noqa: E501
+        self._total_hits = None
+        self._results = None
+        self.discriminator = None
+        if total_hits is not None:
+            self.total_hits = total_hits
+        if results is not None:
+            self.results = results
+
+    @property
+    def total_hits(self):
+        """Gets the total_hits of this SearchResultWorkflowSummary.  # noqa: E501
+
+
+        :return: The total_hits of this SearchResultWorkflowSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._total_hits
+
+    @total_hits.setter
+    def total_hits(self, total_hits):
+        """Sets the total_hits of this SearchResultWorkflowSummary.
+
+
+        :param total_hits: The total_hits of this SearchResultWorkflowSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._total_hits = total_hits
+
+    @property
+    def results(self):
+        """Gets the results of this SearchResultWorkflowSummary.  # noqa: E501
+
+
+        :return: The results of this SearchResultWorkflowSummary.  # noqa: E501
+        :rtype: list[WorkflowSummary]
+        """
+        return self._results
+
+    @results.setter
+    def results(self, results):
+        """Sets the results of this SearchResultWorkflowSummary.
+
+
+        :param results: The results of this SearchResultWorkflowSummary.  # noqa: E501
+        :type: list[WorkflowSummary]
+        """
+
+        self._results = results
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SearchResultWorkflowSummary, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SearchResultWorkflowSummary):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/skip_task_request.py b/omagent_core/engine/http/models/skip_task_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3b37616d1dee0ac0a88699e3476844e4871f572
--- /dev/null
+++ b/omagent_core/engine/http/models/skip_task_request.py
@@ -0,0 +1,127 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SkipTaskRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "task_input": "dict(str, object)",
+        "task_output": "dict(str, object)",
+    }
+
+    attribute_map = {"task_input": "taskInput", "task_output": "taskOutput"}
+
+    def __init__(self, task_input=None, task_output=None):  # noqa: E501
+        """SkipTaskRequest - a model defined in Swagger"""  # noqa: E501
+        self._task_input = None
+        self._task_output = None
+        self.discriminator = None
+        if task_input is not None:
+            self.task_input = task_input
+        if task_output is not None:
+            self.task_output = task_output
+
+    @property
+    def task_input(self):
+        """Gets the task_input of this SkipTaskRequest.  # noqa: E501
+
+
+        :return: The task_input of this SkipTaskRequest.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._task_input
+
+    @task_input.setter
+    def task_input(self, task_input):
+        """Sets the task_input of this SkipTaskRequest.
+
+
+        :param task_input: The task_input of this SkipTaskRequest.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._task_input = task_input
+
+    @property
+    def task_output(self):
+        """Gets the task_output of this SkipTaskRequest.  # noqa: E501
+
+
+        :return: The task_output of this SkipTaskRequest.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._task_output
+
+    @task_output.setter
+    def task_output(self, task_output):
+        """Sets the task_output of this SkipTaskRequest.
+
+
+        :param task_output: The task_output of this SkipTaskRequest.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._task_output = task_output
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SkipTaskRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SkipTaskRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/start_workflow.py b/omagent_core/engine/http/models/start_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..6a8aaac9c6466402697dc9cd5d3adefd0a2a8efb
--- /dev/null
+++ b/omagent_core/engine/http/models/start_workflow.py
@@ -0,0 +1,215 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class StartWorkflow(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "name": "str",
+        "version": "int",
+        "correlation_id": "str",
+        "input": "dict(str, object)",
+        "task_to_domain": "dict(str, str)",
+    }
+
+    attribute_map = {
+        "name": "name",
+        "version": "version",
+        "correlation_id": "correlationId",
+        "input": "input",
+        "task_to_domain": "taskToDomain",
+    }
+
+    def __init__(
+        self,
+        name=None,
+        version=None,
+        correlation_id=None,
+        input=None,
+        task_to_domain=None,
+    ):  # noqa: E501
+        """StartWorkflow - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._version = None
+        self._correlation_id = None
+        self._input = None
+        self._task_to_domain = None
+        self.discriminator = None
+        if name is not None:
+            self.name = name
+        if version is not None:
+            self.version = version
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if input is not None:
+            self.input = input
+        if task_to_domain is not None:
+            self.task_to_domain = task_to_domain
+
+    @property
+    def name(self):
+        """Gets the name of this StartWorkflow.  # noqa: E501
+
+
+        :return: The name of this StartWorkflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this StartWorkflow.
+
+
+        :param name: The name of this StartWorkflow.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def version(self):
+        """Gets the version of this StartWorkflow.  # noqa: E501
+
+
+        :return: The version of this StartWorkflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._version
+
+    @version.setter
+    def version(self, version):
+        """Sets the version of this StartWorkflow.
+
+
+        :param version: The version of this StartWorkflow.  # noqa: E501
+        :type: int
+        """
+
+        self._version = version
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this StartWorkflow.  # noqa: E501
+
+
+        :return: The correlation_id of this StartWorkflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this StartWorkflow.
+
+
+        :param correlation_id: The correlation_id of this StartWorkflow.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def input(self):
+        """Gets the input of this StartWorkflow.  # noqa: E501
+
+
+        :return: The input of this StartWorkflow.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input
+
+    @input.setter
+    def input(self, input):
+        """Sets the input of this StartWorkflow.
+
+
+        :param input: The input of this StartWorkflow.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input = input
+
+    @property
+    def task_to_domain(self):
+        """Gets the task_to_domain of this StartWorkflow.  # noqa: E501
+
+
+        :return: The task_to_domain of this StartWorkflow.  # noqa: E501
+        :rtype: dict(str, str)
+        """
+        return self._task_to_domain
+
+    @task_to_domain.setter
+    def task_to_domain(self, task_to_domain):
+        """Sets the task_to_domain of this StartWorkflow.
+
+
+        :param task_to_domain: The task_to_domain of this StartWorkflow.  # noqa: E501
+        :type: dict(str, str)
+        """
+
+        self._task_to_domain = task_to_domain
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(StartWorkflow, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, StartWorkflow):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/start_workflow_request.py b/omagent_core/engine/http/models/start_workflow_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..24a7981d06673da2e2895c8e5f4e2fad6abe12bf
--- /dev/null
+++ b/omagent_core/engine/http/models/start_workflow_request.py
@@ -0,0 +1,360 @@
+import pprint
+import re  # noqa: F401
+from enum import Enum
+
+import six
+
+
+class IdempotencyStrategy(str, Enum):
+    FAIL = ("FAIL",)
+    RETURN_EXISTING = "RETURN_EXISTING"
+
+    def __str__(self) -> str:
+        return self.name.__str__()
+
+
+class StartWorkflowRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "name": "str",
+        "version": "int",
+        "correlation_id": "str",
+        "input": "dict(str, object)",
+        "task_to_domain": "dict(str, str)",
+        "workflow_def": "WorkflowDef",
+        "external_input_payload_storage_path": "str",
+        "priority": "int",
+        "created_by": "str",
+        "idempotency_key": "str",
+        "idempotency_strategy": "str",
+    }
+
+    attribute_map = {
+        "name": "name",
+        "version": "version",
+        "correlation_id": "correlationId",
+        "input": "input",
+        "task_to_domain": "taskToDomain",
+        "workflow_def": "workflowDef",
+        "external_input_payload_storage_path": "externalInputPayloadStoragePath",
+        "priority": "priority",
+        "created_by": "createdBy",
+        "idempotency_key": "idempotencyKey",
+        "idempotency_strategy": "idempotencyStrategy",
+    }
+
+    def __init__(
+        self,
+        name=None,
+        version=None,
+        correlation_id=None,
+        input=None,
+        task_to_domain=None,
+        workflow_def=None,
+        external_input_payload_storage_path=None,
+        priority=None,
+        created_by=None,
+        idempotency_key: str = None,
+        idempotency_strategy: IdempotencyStrategy = IdempotencyStrategy.FAIL,
+    ):  # noqa: E501
+        """StartWorkflowRequest - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._version = None
+        self._correlation_id = None
+        self._input = None
+        self._task_to_domain = None
+        self._workflow_def = None
+        self._external_input_payload_storage_path = None
+        self._priority = None
+        self._created_by = None
+        self.discriminator = None
+        self.name = name
+        if version is not None:
+            self.version = version
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if input is not None:
+            self.input = input
+        if task_to_domain is not None:
+            self.task_to_domain = task_to_domain
+        if workflow_def is not None:
+            self.workflow_def = workflow_def
+        if external_input_payload_storage_path is not None:
+            self.external_input_payload_storage_path = (
+                external_input_payload_storage_path
+            )
+        if priority is not None:
+            self.priority = priority
+        if created_by is not None:
+            self.created_by = created_by
+        if idempotency_key is not None:
+            self._idempotency_key = idempotency_key
+            self._idempotency_strategy = idempotency_strategy
+        else:
+            self._idempotency_key = None
+            self._idempotency_strategy = IdempotencyStrategy.FAIL
+
+    @property
+    def name(self):
+        """Gets the name of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The name of this StartWorkflowRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this StartWorkflowRequest.
+
+
+        :param name: The name of this StartWorkflowRequest.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def version(self):
+        """Gets the version of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The version of this StartWorkflowRequest.  # noqa: E501
+        :rtype: int
+        """
+        return self._version
+
+    @version.setter
+    def version(self, version):
+        """Sets the version of this StartWorkflowRequest.
+
+
+        :param version: The version of this StartWorkflowRequest.  # noqa: E501
+        :type: int
+        """
+
+        self._version = version
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The correlation_id of this StartWorkflowRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this StartWorkflowRequest.
+
+
+        :param correlation_id: The correlation_id of this StartWorkflowRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def input(self):
+        """Gets the input of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The input of this StartWorkflowRequest.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input
+
+    @input.setter
+    def input(self, input):
+        """Sets the input of this StartWorkflowRequest.
+
+
+        :param input: The input of this StartWorkflowRequest.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input = input
+
+    @property
+    def task_to_domain(self):
+        """Gets the task_to_domain of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The task_to_domain of this StartWorkflowRequest.  # noqa: E501
+        :rtype: dict(str, str)
+        """
+        return self._task_to_domain
+
+    @task_to_domain.setter
+    def task_to_domain(self, task_to_domain):
+        """Sets the task_to_domain of this StartWorkflowRequest.
+
+
+        :param task_to_domain: The task_to_domain of this StartWorkflowRequest.  # noqa: E501
+        :type: dict(str, str)
+        """
+
+        self._task_to_domain = task_to_domain
+
+    @property
+    def workflow_def(self):
+        """Gets the workflow_def of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The workflow_def of this StartWorkflowRequest.  # noqa: E501
+        :rtype: WorkflowDef
+        """
+        return self._workflow_def
+
+    @workflow_def.setter
+    def workflow_def(self, workflow_def):
+        """Sets the workflow_def of this StartWorkflowRequest.
+
+
+        :param workflow_def: The workflow_def of this StartWorkflowRequest.  # noqa: E501
+        :type: WorkflowDef
+        """
+
+        self._workflow_def = workflow_def
+
+    @property
+    def external_input_payload_storage_path(self):
+        """Gets the external_input_payload_storage_path of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The external_input_payload_storage_path of this StartWorkflowRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_input_payload_storage_path
+
+    @external_input_payload_storage_path.setter
+    def external_input_payload_storage_path(self, external_input_payload_storage_path):
+        """Sets the external_input_payload_storage_path of this StartWorkflowRequest.
+
+
+        :param external_input_payload_storage_path: The external_input_payload_storage_path of this StartWorkflowRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._external_input_payload_storage_path = external_input_payload_storage_path
+
+    @property
+    def priority(self):
+        """Gets the priority of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The priority of this StartWorkflowRequest.  # noqa: E501
+        :rtype: int
+        """
+        return self._priority
+
+    @priority.setter
+    def priority(self, priority):
+        """Sets the priority of this StartWorkflowRequest.
+
+
+        :param priority: The priority of this StartWorkflowRequest.  # noqa: E501
+        :type: int
+        """
+
+        self._priority = priority
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this StartWorkflowRequest.  # noqa: E501
+
+
+        :return: The created_by of this StartWorkflowRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this StartWorkflowRequest.
+
+
+        :param created_by: The created_by of this StartWorkflowRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def idempotency_key(self):
+        return self._idempotency_key
+
+    @idempotency_key.setter
+    def idempotency_key(self, idempotency_key: str):
+        self._idempotency_key = idempotency_key
+
+    @property
+    def idempotency_strategy(self) -> IdempotencyStrategy:
+        return self._idempotency_strategy
+
+    @idempotency_strategy.setter
+    def idempotency_strategy(self, idempotency_strategy: IdempotencyStrategy):
+        self._idempotency_strategy = idempotency_strategy
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(StartWorkflowRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, StartWorkflowRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/state_change_event.py b/omagent_core/engine/http/models/state_change_event.py
new file mode 100644
index 0000000000000000000000000000000000000000..0478b75242942a4351eeba744d5c8cfa60508fcc
--- /dev/null
+++ b/omagent_core/engine/http/models/state_change_event.py
@@ -0,0 +1,76 @@
+from enum import Enum
+from typing import List, Union
+
+from typing_extensions import Self
+
+
+class StateChangeEventType(Enum):
+    onScheduled = "onScheduled"
+    onStart = "onStart"
+    onFailed = "onFailed"
+    onSuccess = "onSuccess"
+    onCancelled = "onCancelled"
+
+
+class StateChangeEvent:
+    swagger_types = {"type": "str", "payload": "dict[str, object]"}
+
+    attribute_map = {"type": "type", "payload": "payload"}
+
+    def __init__(self, type: str, payload: dict[str, object]) -> None:
+        self._type = type
+        self._payload = payload
+
+    @property
+    def type(self):
+        return self._type
+
+    @type.setter
+    def type(self, type: str) -> Self:
+        self._type = type
+
+    @property
+    def payload(self):
+        return self._payload
+
+    @payload.setter
+    def payload(self, payload: dict[str, object]) -> Self:
+        self._payload = payload
+
+
+class StateChangeConfig:
+    swagger_types = {"type": "str", "events": "list[StateChangeEvent]"}
+
+    attribute_map = {"type": "type", "events": "events"}
+
+    def __init__(
+        self,
+        event_type: Union[str, StateChangeEventType, List[StateChangeEventType]] = None,
+        events: List[StateChangeEvent] = None,
+    ) -> None:
+        if event_type is None:
+            return
+        if isinstance(event_type, list):
+            str_values = []
+            for et in event_type:
+                str_values.append(et.name)
+            self._type = ",".join(str_values)
+        else:
+            self._type = event_type.name
+        self._events = events
+
+    @property
+    def type(self):
+        return self._type
+
+    @type.setter
+    def type(self, event_type: StateChangeEventType) -> Self:
+        self._type = event_type.name
+
+    @property
+    def events(self):
+        return self._events
+
+    @events.setter
+    def events(self, events: List[StateChangeEvent]) -> Self:
+        self._events = events
diff --git a/omagent_core/engine/http/models/sub_workflow_params.py b/omagent_core/engine/http/models/sub_workflow_params.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0902458daa0fca21fe6c3edd3ce598d22493382
--- /dev/null
+++ b/omagent_core/engine/http/models/sub_workflow_params.py
@@ -0,0 +1,182 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class SubWorkflowParams(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "name": "str",
+        "version": "int",
+        "task_to_domain": "dict(str, str)",
+        "workflow_definition": "WorkflowDef",
+    }
+
+    attribute_map = {
+        "name": "name",
+        "version": "version",
+        "task_to_domain": "taskToDomain",
+        "workflow_definition": "workflowDefinition",
+    }
+
+    def __init__(
+        self, name=None, version=None, task_to_domain=None, workflow_definition=None
+    ):  # noqa: E501
+        """SubWorkflowParams - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._version = None
+        self._task_to_domain = None
+        self._workflow_definition = None
+        self.discriminator = None
+        self.name = name
+        if version is not None:
+            self.version = version
+        if task_to_domain is not None:
+            self.task_to_domain = task_to_domain
+        if workflow_definition is not None:
+            self.workflow_definition = workflow_definition
+
+    @property
+    def name(self):
+        """Gets the name of this SubWorkflowParams.  # noqa: E501
+
+
+        :return: The name of this SubWorkflowParams.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this SubWorkflowParams.
+
+
+        :param name: The name of this SubWorkflowParams.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def version(self):
+        """Gets the version of this SubWorkflowParams.  # noqa: E501
+
+
+        :return: The version of this SubWorkflowParams.  # noqa: E501
+        :rtype: int
+        """
+        return self._version
+
+    @version.setter
+    def version(self, version):
+        """Sets the version of this SubWorkflowParams.
+
+
+        :param version: The version of this SubWorkflowParams.  # noqa: E501
+        :type: int
+        """
+
+        self._version = version
+
+    @property
+    def task_to_domain(self):
+        """Gets the task_to_domain of this SubWorkflowParams.  # noqa: E501
+
+
+        :return: The task_to_domain of this SubWorkflowParams.  # noqa: E501
+        :rtype: dict(str, str)
+        """
+        return self._task_to_domain
+
+    @task_to_domain.setter
+    def task_to_domain(self, task_to_domain):
+        """Sets the task_to_domain of this SubWorkflowParams.
+
+
+        :param task_to_domain: The task_to_domain of this SubWorkflowParams.  # noqa: E501
+        :type: dict(str, str)
+        """
+
+        self._task_to_domain = task_to_domain
+
+    @property
+    def workflow_definition(self):
+        """Gets the workflow_definition of this SubWorkflowParams.  # noqa: E501
+
+
+        :return: The workflow_definition of this SubWorkflowParams.  # noqa: E501
+        :rtype: WorkflowDef
+        """
+        return self._workflow_definition
+
+    @workflow_definition.setter
+    def workflow_definition(self, workflow_definition):
+        """Sets the workflow_definition of this SubWorkflowParams.
+
+
+        :param workflow_definition: The workflow_definition of this SubWorkflowParams.  # noqa: E501
+        :type: WorkflowDef
+        """
+
+        self._workflow_definition = workflow_definition
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SubWorkflowParams, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SubWorkflowParams):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/subject_ref.py b/omagent_core/engine/http/models/subject_ref.py
new file mode 100644
index 0000000000000000000000000000000000000000..f7e2ad993e981da217d594f5fefe21238773ab92
--- /dev/null
+++ b/omagent_core/engine/http/models/subject_ref.py
@@ -0,0 +1,139 @@
+import pprint
+import re  # noqa: F401
+from enum import Enum
+
+import six
+
+
+class SubjectType(str, Enum):
+    USER = ("USER",)
+    ROLE = ("ROLE",)
+    GROUP = ("GROUP",)
+    TAG = "TAG"
+
+
+class SubjectRef(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"type": "str", "id": "str"}
+
+    attribute_map = {"type": "type", "id": "id"}
+
+    def __init__(self, type=None, id=None):  # noqa: E501
+        """SubjectRef - a model defined in Swagger"""  # noqa: E501
+        self._type = None
+        self._id = None
+        self.discriminator = None
+        if type is not None:
+            self.type = type
+        self.id = id
+
+    @property
+    def type(self):
+        """Gets the type of this SubjectRef.  # noqa: E501
+
+        User, role or group  # noqa: E501
+
+        :return: The type of this SubjectRef.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this SubjectRef.
+
+        User, role or group  # noqa: E501
+
+        :param type: The type of this SubjectRef.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["USER", "ROLE", "GROUP"]  # noqa: E501
+        if type not in allowed_values:
+            raise ValueError(
+                "Invalid value for `type` ({0}), must be one of {1}".format(  # noqa: E501
+                    type, allowed_values
+                )
+            )
+
+        self._type = type
+
+    @property
+    def id(self):
+        """Gets the id of this SubjectRef.  # noqa: E501
+
+
+        :return: The id of this SubjectRef.  # noqa: E501
+        :rtype: str
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this SubjectRef.
+
+
+        :param id: The id of this SubjectRef.  # noqa: E501
+        :type: str
+        """
+        self._id = id
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(SubjectRef, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, SubjectRef):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/tag_object.py b/omagent_core/engine/http/models/tag_object.py
new file mode 100644
index 0000000000000000000000000000000000000000..7edbfd6104eda03dccb59ae30e4e0dda1d0477f8
--- /dev/null
+++ b/omagent_core/engine/http/models/tag_object.py
@@ -0,0 +1,157 @@
+# coding: utf-8
+
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class TagObject(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"key": "str", "type": "str", "value": "object"}
+
+    attribute_map = {"key": "key", "type": "type", "value": "value"}
+
+    def __init__(self, key=None, type=None, value=None):  # noqa: E501
+        """TagObject - a model defined in Swagger"""  # noqa: E501
+        self._key = None
+        self._type = None
+        self._value = None
+        self.discriminator = None
+        if key is not None:
+            self.key = key
+        if type is not None:
+            self.type = type
+        if value is not None:
+            self.value = value
+
+    @property
+    def key(self):
+        """Gets the key of this TagObject.  # noqa: E501
+
+
+        :return: The key of this TagObject.  # noqa: E501
+        :rtype: str
+        """
+        return self._key
+
+    @key.setter
+    def key(self, key):
+        """Sets the key of this TagObject.
+
+
+        :param key: The key of this TagObject.  # noqa: E501
+        :type: str
+        """
+
+        self._key = key
+
+    @property
+    def type(self):
+        """Gets the type of this TagObject.  # noqa: E501
+
+
+        :return: The type of this TagObject.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this TagObject.
+
+
+        :param type: The type of this TagObject.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["METADATA", "RATE_LIMIT"]  # noqa: E501
+        if type not in allowed_values:
+            raise ValueError(
+                "Invalid value for `type` ({0}), must be one of {1}".format(  # noqa: E501
+                    type, allowed_values
+                )
+            )
+
+        self._type = type
+
+    @property
+    def value(self):
+        """Gets the value of this TagObject.  # noqa: E501
+
+
+        :return: The value of this TagObject.  # noqa: E501
+        :rtype: object
+        """
+        return self._value
+
+    @value.setter
+    def value(self, value):
+        """Sets the value of this TagObject.
+
+
+        :param value: The value of this TagObject.  # noqa: E501
+        :type: object
+        """
+
+        self._value = value
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TagObject, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TagObject):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/tag_string.py b/omagent_core/engine/http/models/tag_string.py
new file mode 100644
index 0000000000000000000000000000000000000000..1d45ca99efcb33fdf18f9a5e60c2db7765416533
--- /dev/null
+++ b/omagent_core/engine/http/models/tag_string.py
@@ -0,0 +1,157 @@
+# coding: utf-8
+
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class TagString(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"key": "str", "type": "str", "value": "str"}
+
+    attribute_map = {"key": "key", "type": "type", "value": "value"}
+
+    def __init__(self, key=None, type=None, value=None):  # noqa: E501
+        """TagString - a model defined in Swagger"""  # noqa: E501
+        self._key = None
+        self._type = None
+        self._value = None
+        self.discriminator = None
+        if key is not None:
+            self.key = key
+        if type is not None:
+            self.type = type
+        if value is not None:
+            self.value = value
+
+    @property
+    def key(self):
+        """Gets the key of this TagString.  # noqa: E501
+
+
+        :return: The key of this TagString.  # noqa: E501
+        :rtype: str
+        """
+        return self._key
+
+    @key.setter
+    def key(self, key):
+        """Sets the key of this TagString.
+
+
+        :param key: The key of this TagString.  # noqa: E501
+        :type: str
+        """
+
+        self._key = key
+
+    @property
+    def type(self):
+        """Gets the type of this TagString.  # noqa: E501
+
+
+        :return: The type of this TagString.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this TagString.
+
+
+        :param type: The type of this TagString.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["METADATA", "RATE_LIMIT"]  # noqa: E501
+        if type not in allowed_values:
+            raise ValueError(
+                "Invalid value for `type` ({0}), must be one of {1}".format(  # noqa: E501
+                    type, allowed_values
+                )
+            )
+
+        self._type = type
+
+    @property
+    def value(self):
+        """Gets the value of this TagString.  # noqa: E501
+
+
+        :return: The value of this TagString.  # noqa: E501
+        :rtype: str
+        """
+        return self._value
+
+    @value.setter
+    def value(self, value):
+        """Sets the value of this TagString.
+
+
+        :param value: The value of this TagString.  # noqa: E501
+        :type: str
+        """
+
+        self._value = value
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TagString, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TagString):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/target_ref.py b/omagent_core/engine/http/models/target_ref.py
new file mode 100644
index 0000000000000000000000000000000000000000..83942fd90dd8adbccc54baddb3decbc0f6767dde
--- /dev/null
+++ b/omagent_core/engine/http/models/target_ref.py
@@ -0,0 +1,139 @@
+import pprint
+import re  # noqa: F401
+from enum import Enum
+
+import six
+
+
+class TargetType(str, Enum):
+    WORKFLOW_DEF = ("WORKFLOW_DEF",)
+    TASK_DEF = ("TASK_DEF",)
+    APPLICATION = ("APPLICATION",)
+    USER = ("USER",)
+    SECRET = ("SECRET",)
+    TAG = ("TAG",)
+    DOMAIN = "DOMAIN"
+
+
+class TargetRef(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"type": "str", "id": "str"}
+
+    attribute_map = {"type": "type", "id": "id"}
+
+    def __init__(self, type=None, id=None):  # noqa: E501
+        """TargetRef - a model defined in Swagger"""  # noqa: E501
+        self._type = None
+        self._id = None
+        self.discriminator = None
+        self.type = type
+        self.id = id
+
+    @property
+    def type(self):
+        """Gets the type of this TargetRef.  # noqa: E501
+
+
+        :return: The type of this TargetRef.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this TargetRef.
+
+
+        :param type: The type of this TargetRef.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [t.value for t in TargetType]  # noqa: E501
+        if type not in allowed_values:
+            raise ValueError(
+                "Invalid value for `type` ({0}), must be one of {1}".format(  # noqa: E501
+                    type, allowed_values
+                )
+            )
+
+        self._type = type
+
+    @property
+    def id(self):
+        """Gets the id of this TargetRef.  # noqa: E501
+
+
+        :return: The id of this TargetRef.  # noqa: E501
+        :rtype: str
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this TargetRef.
+
+
+        :param id: The id of this TargetRef.  # noqa: E501
+        :type: str
+        """
+        self._id = id
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TargetRef, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TargetRef):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/task.py b/omagent_core/engine/http/models/task.py
new file mode 100644
index 0000000000000000000000000000000000000000..55a69db82db57e4c64cce1d962b9b1b83d5e7320
--- /dev/null
+++ b/omagent_core/engine/http/models/task.py
@@ -0,0 +1,1268 @@
+import pprint
+import re  # noqa: F401
+
+import six
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.http.models.task_result import TaskResult
+from omagent_core.engine.http.models.task_result_status import TaskResultStatus
+
+
+class Task(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "task_type": "str",
+        "status": "str",
+        "input_data": "dict(str, object)",
+        "reference_task_name": "str",
+        "retry_count": "int",
+        "seq": "int",
+        "correlation_id": "str",
+        "poll_count": "int",
+        "task_def_name": "str",
+        "scheduled_time": "int",
+        "start_time": "int",
+        "end_time": "int",
+        "update_time": "int",
+        "start_delay_in_seconds": "int",
+        "retried_task_id": "str",
+        "retried": "bool",
+        "executed": "bool",
+        "callback_from_worker": "bool",
+        "response_timeout_seconds": "int",
+        "workflow_instance_id": "str",
+        "workflow_type": "str",
+        "task_id": "str",
+        "reason_for_incompletion": "str",
+        "callback_after_seconds": "int",
+        "worker_id": "str",
+        "output_data": "dict(str, object)",
+        "workflow_task": "WorkflowTask",
+        "domain": "str",
+        "rate_limit_per_frequency": "int",
+        "rate_limit_frequency_in_seconds": "int",
+        "external_input_payload_storage_path": "str",
+        "external_output_payload_storage_path": "str",
+        "workflow_priority": "int",
+        "execution_name_space": "str",
+        "isolation_group_id": "str",
+        "iteration": "int",
+        "sub_workflow_id": "str",
+        "subworkflow_changed": "bool",
+        "loop_over_task": "bool",
+        "task_definition": "TaskDef",
+        "queue_wait_time": "int",
+        "biz_meta": "str",
+        "callback_url": "str",
+        "conversation_info": "dict(str, object)",
+    }
+
+    attribute_map = {
+        "task_type": "taskType",
+        "status": "status",
+        "input_data": "inputData",
+        "reference_task_name": "referenceTaskName",
+        "retry_count": "retryCount",
+        "seq": "seq",
+        "correlation_id": "correlationId",
+        "poll_count": "pollCount",
+        "task_def_name": "taskDefName",
+        "scheduled_time": "scheduledTime",
+        "start_time": "startTime",
+        "end_time": "endTime",
+        "update_time": "updateTime",
+        "start_delay_in_seconds": "startDelayInSeconds",
+        "retried_task_id": "retriedTaskId",
+        "retried": "retried",
+        "executed": "executed",
+        "callback_from_worker": "callbackFromWorker",
+        "response_timeout_seconds": "responseTimeoutSeconds",
+        "workflow_instance_id": "workflowInstanceId",
+        "workflow_type": "workflowType",
+        "task_id": "taskId",
+        "reason_for_incompletion": "reasonForIncompletion",
+        "callback_after_seconds": "callbackAfterSeconds",
+        "worker_id": "workerId",
+        "output_data": "outputData",
+        "workflow_task": "workflowTask",
+        "domain": "domain",
+        "rate_limit_per_frequency": "rateLimitPerFrequency",
+        "rate_limit_frequency_in_seconds": "rateLimitFrequencyInSeconds",
+        "external_input_payload_storage_path": "externalInputPayloadStoragePath",
+        "external_output_payload_storage_path": "externalOutputPayloadStoragePath",
+        "workflow_priority": "workflowPriority",
+        "execution_name_space": "executionNameSpace",
+        "isolation_group_id": "isolationGroupId",
+        "iteration": "iteration",
+        "sub_workflow_id": "subWorkflowId",
+        "subworkflow_changed": "subworkflowChanged",
+        "loop_over_task": "loopOverTask",
+        "task_definition": "taskDefinition",
+        "queue_wait_time": "queueWaitTime",
+        "biz_meta": "bizMeta",
+        "callback_url": "callbackUrl",
+        "conversation_info": "conversationInfo",
+    }
+
+    def __init__(
+        self,
+        task_type=None,
+        status=None,
+        input_data=None,
+        reference_task_name=None,
+        retry_count=None,
+        seq=None,
+        correlation_id=None,
+        poll_count=None,
+        task_def_name=None,
+        scheduled_time=None,
+        start_time=None,
+        end_time=None,
+        update_time=None,
+        start_delay_in_seconds=None,
+        retried_task_id=None,
+        retried=None,
+        executed=None,
+        callback_from_worker=None,
+        response_timeout_seconds=None,
+        workflow_instance_id=None,
+        workflow_type=None,
+        task_id=None,
+        reason_for_incompletion=None,
+        callback_after_seconds=None,
+        worker_id=None,
+        output_data=None,
+        workflow_task=None,
+        domain=None,
+        rate_limit_per_frequency=None,
+        rate_limit_frequency_in_seconds=None,
+        external_input_payload_storage_path=None,
+        external_output_payload_storage_path=None,
+        workflow_priority=None,
+        execution_name_space=None,
+        isolation_group_id=None,
+        iteration=None,
+        sub_workflow_id=None,
+        subworkflow_changed=None,
+        loop_over_task=None,
+        task_definition=None,
+        queue_wait_time=None,
+        biz_meta=None,
+        callback_url=None,
+        conversation_info=None,
+    ):  # noqa: E501
+        """Task - a model defined in Swagger"""  # noqa: E501
+        self._task_type = None
+        self._status = None
+        self._input_data = None
+        self._reference_task_name = None
+        self._retry_count = None
+        self._seq = None
+        self._correlation_id = None
+        self._poll_count = None
+        self._task_def_name = None
+        self._scheduled_time = None
+        self._start_time = None
+        self._end_time = None
+        self._update_time = None
+        self._start_delay_in_seconds = None
+        self._retried_task_id = None
+        self._retried = None
+        self._executed = None
+        self._callback_from_worker = None
+        self._response_timeout_seconds = None
+        self._workflow_instance_id = None
+        self._workflow_type = None
+        self._task_id = None
+        self._reason_for_incompletion = None
+        self._callback_after_seconds = None
+        self._worker_id = None
+        self._output_data = None
+        self._workflow_task = None
+        self._domain = None
+        self._rate_limit_per_frequency = None
+        self._rate_limit_frequency_in_seconds = None
+        self._external_input_payload_storage_path = None
+        self._external_output_payload_storage_path = None
+        self._workflow_priority = None
+        self._execution_name_space = None
+        self._isolation_group_id = None
+        self._iteration = None
+        self._sub_workflow_id = None
+        self._subworkflow_changed = None
+        self._loop_over_task = None
+        self._task_definition = None
+        self._queue_wait_time = None
+        self.discriminator = None
+        self.biz_meta = None
+        self.callback_url = None
+        self.conversation_info = None
+        if task_type is not None:
+            self.task_type = task_type
+        if status is not None:
+            self.status = status
+        if input_data is not None:
+            self.input_data = input_data
+        if reference_task_name is not None:
+            self.reference_task_name = reference_task_name
+        if retry_count is not None:
+            self.retry_count = retry_count
+        if seq is not None:
+            self.seq = seq
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if poll_count is not None:
+            self.poll_count = poll_count
+        if task_def_name is not None:
+            self.task_def_name = task_def_name
+        if scheduled_time is not None:
+            self.scheduled_time = scheduled_time
+        if start_time is not None:
+            self.start_time = start_time
+        if end_time is not None:
+            self.end_time = end_time
+        if update_time is not None:
+            self.update_time = update_time
+        if start_delay_in_seconds is not None:
+            self.start_delay_in_seconds = start_delay_in_seconds
+        if retried_task_id is not None:
+            self.retried_task_id = retried_task_id
+        if retried is not None:
+            self.retried = retried
+        if executed is not None:
+            self.executed = executed
+        if callback_from_worker is not None:
+            self.callback_from_worker = callback_from_worker
+        if response_timeout_seconds is not None:
+            self.response_timeout_seconds = response_timeout_seconds
+        if workflow_instance_id is not None:
+            self.workflow_instance_id = workflow_instance_id
+        if workflow_type is not None:
+            self.workflow_type = workflow_type
+        if task_id is not None:
+            self.task_id = task_id
+        if reason_for_incompletion is not None:
+            self.reason_for_incompletion = reason_for_incompletion
+        if callback_after_seconds is not None:
+            self.callback_after_seconds = callback_after_seconds
+        if worker_id is not None:
+            self.worker_id = worker_id
+        if output_data is not None:
+            self.output_data = output_data
+        if workflow_task is not None:
+            self.workflow_task = workflow_task
+        if domain is not None:
+            self.domain = domain
+        if rate_limit_per_frequency is not None:
+            self.rate_limit_per_frequency = rate_limit_per_frequency
+        if rate_limit_frequency_in_seconds is not None:
+            self.rate_limit_frequency_in_seconds = rate_limit_frequency_in_seconds
+        if external_input_payload_storage_path is not None:
+            self.external_input_payload_storage_path = (
+                external_input_payload_storage_path
+            )
+        if external_output_payload_storage_path is not None:
+            self.external_output_payload_storage_path = (
+                external_output_payload_storage_path
+            )
+        if workflow_priority is not None:
+            self.workflow_priority = workflow_priority
+        if execution_name_space is not None:
+            self.execution_name_space = execution_name_space
+        if isolation_group_id is not None:
+            self.isolation_group_id = isolation_group_id
+        if iteration is not None:
+            self.iteration = iteration
+        if sub_workflow_id is not None:
+            self.sub_workflow_id = sub_workflow_id
+        if subworkflow_changed is not None:
+            self.subworkflow_changed = subworkflow_changed
+        if loop_over_task is not None:
+            self.loop_over_task = loop_over_task
+        if task_definition is not None:
+            self.task_definition = task_definition
+        if queue_wait_time is not None:
+            self.queue_wait_time = queue_wait_time
+        if biz_meta is not None:
+            self.biz_meta = biz_meta
+        if callback_url is not None:
+            self.callback_url = callback_url
+        if conversation_info is not None:
+            self.conversation_info = conversation_info
+            
+    @property
+    def task_type(self):
+        """Gets the task_type of this Task.  # noqa: E501
+
+
+        :return: The task_type of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_type
+
+    @task_type.setter
+    def task_type(self, task_type):
+        """Sets the task_type of this Task.
+
+
+        :param task_type: The task_type of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._task_type = task_type
+
+    @property
+    def status(self):
+        """Gets the status of this Task.  # noqa: E501
+
+
+        :return: The status of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    @status.setter
+    def status(self, status):
+        """Sets the status of this Task.
+
+
+        :param status: The status of this Task.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            "IN_PROGRESS",
+            "CANCELED",
+            "FAILED",
+            "FAILED_WITH_TERMINAL_ERROR",
+            "COMPLETED",
+            "COMPLETED_WITH_ERRORS",
+            "SCHEDULED",
+            "TIMED_OUT",
+            "SKIPPED",
+        ]  # noqa: E501
+        if status not in allowed_values:
+            raise ValueError(
+                "Invalid value for `status` ({0}), must be one of {1}".format(  # noqa: E501
+                    status, allowed_values
+                )
+            )
+
+        self._status = status
+
+    @property
+    def input_data(self):
+        """Gets the input_data of this Task.  # noqa: E501
+
+
+        :return: The input_data of this Task.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input_data
+
+    @input_data.setter
+    def input_data(self, input_data):
+        """Sets the input_data of this Task.
+
+
+        :param input_data: The input_data of this Task.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input_data = input_data
+
+    @property
+    def reference_task_name(self):
+        """Gets the reference_task_name of this Task.  # noqa: E501
+
+
+        :return: The reference_task_name of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._reference_task_name
+
+    @reference_task_name.setter
+    def reference_task_name(self, reference_task_name):
+        """Sets the reference_task_name of this Task.
+
+
+        :param reference_task_name: The reference_task_name of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._reference_task_name = reference_task_name
+
+    @property
+    def retry_count(self):
+        """Gets the retry_count of this Task.  # noqa: E501
+
+
+        :return: The retry_count of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._retry_count
+
+    @retry_count.setter
+    def retry_count(self, retry_count):
+        """Sets the retry_count of this Task.
+
+
+        :param retry_count: The retry_count of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._retry_count = retry_count
+
+    @property
+    def seq(self):
+        """Gets the seq of this Task.  # noqa: E501
+
+
+        :return: The seq of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._seq
+
+    @seq.setter
+    def seq(self, seq):
+        """Sets the seq of this Task.
+
+
+        :param seq: The seq of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._seq = seq
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this Task.  # noqa: E501
+
+
+        :return: The correlation_id of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this Task.
+
+
+        :param correlation_id: The correlation_id of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def poll_count(self):
+        """Gets the poll_count of this Task.  # noqa: E501
+
+
+        :return: The poll_count of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._poll_count
+
+    @poll_count.setter
+    def poll_count(self, poll_count):
+        """Sets the poll_count of this Task.
+
+
+        :param poll_count: The poll_count of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._poll_count = poll_count
+
+    @property
+    def task_def_name(self):
+        """Gets the task_def_name of this Task.  # noqa: E501
+
+
+        :return: The task_def_name of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_def_name
+
+    @task_def_name.setter
+    def task_def_name(self, task_def_name):
+        """Sets the task_def_name of this Task.
+
+
+        :param task_def_name: The task_def_name of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._task_def_name = task_def_name
+
+    @property
+    def scheduled_time(self):
+        """Gets the scheduled_time of this Task.  # noqa: E501
+
+
+        :return: The scheduled_time of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._scheduled_time
+
+    @scheduled_time.setter
+    def scheduled_time(self, scheduled_time):
+        """Sets the scheduled_time of this Task.
+
+
+        :param scheduled_time: The scheduled_time of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._scheduled_time = scheduled_time
+
+    @property
+    def start_time(self):
+        """Gets the start_time of this Task.  # noqa: E501
+
+
+        :return: The start_time of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._start_time
+
+    @start_time.setter
+    def start_time(self, start_time):
+        """Sets the start_time of this Task.
+
+
+        :param start_time: The start_time of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._start_time = start_time
+
+    @property
+    def end_time(self):
+        """Gets the end_time of this Task.  # noqa: E501
+
+
+        :return: The end_time of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._end_time
+
+    @end_time.setter
+    def end_time(self, end_time):
+        """Sets the end_time of this Task.
+
+
+        :param end_time: The end_time of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._end_time = end_time
+
+    @property
+    def update_time(self):
+        """Gets the update_time of this Task.  # noqa: E501
+
+
+        :return: The update_time of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._update_time
+
+    @update_time.setter
+    def update_time(self, update_time):
+        """Sets the update_time of this Task.
+
+
+        :param update_time: The update_time of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._update_time = update_time
+
+    @property
+    def start_delay_in_seconds(self):
+        """Gets the start_delay_in_seconds of this Task.  # noqa: E501
+
+
+        :return: The start_delay_in_seconds of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._start_delay_in_seconds
+
+    @start_delay_in_seconds.setter
+    def start_delay_in_seconds(self, start_delay_in_seconds):
+        """Sets the start_delay_in_seconds of this Task.
+
+
+        :param start_delay_in_seconds: The start_delay_in_seconds of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._start_delay_in_seconds = start_delay_in_seconds
+
+    @property
+    def retried_task_id(self):
+        """Gets the retried_task_id of this Task.  # noqa: E501
+
+
+        :return: The retried_task_id of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._retried_task_id
+
+    @retried_task_id.setter
+    def retried_task_id(self, retried_task_id):
+        """Sets the retried_task_id of this Task.
+
+
+        :param retried_task_id: The retried_task_id of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._retried_task_id = retried_task_id
+
+    @property
+    def retried(self):
+        """Gets the retried of this Task.  # noqa: E501
+
+
+        :return: The retried of this Task.  # noqa: E501
+        :rtype: bool
+        """
+        return self._retried
+
+    @retried.setter
+    def retried(self, retried):
+        """Sets the retried of this Task.
+
+
+        :param retried: The retried of this Task.  # noqa: E501
+        :type: bool
+        """
+
+        self._retried = retried
+
+    @property
+    def executed(self):
+        """Gets the executed of this Task.  # noqa: E501
+
+
+        :return: The executed of this Task.  # noqa: E501
+        :rtype: bool
+        """
+        return self._executed
+
+    @executed.setter
+    def executed(self, executed):
+        """Sets the executed of this Task.
+
+
+        :param executed: The executed of this Task.  # noqa: E501
+        :type: bool
+        """
+
+        self._executed = executed
+
+    @property
+    def callback_from_worker(self):
+        """Gets the callback_from_worker of this Task.  # noqa: E501
+
+
+        :return: The callback_from_worker of this Task.  # noqa: E501
+        :rtype: bool
+        """
+        return self._callback_from_worker
+
+    @callback_from_worker.setter
+    def callback_from_worker(self, callback_from_worker):
+        """Sets the callback_from_worker of this Task.
+
+
+        :param callback_from_worker: The callback_from_worker of this Task.  # noqa: E501
+        :type: bool
+        """
+
+        self._callback_from_worker = callback_from_worker
+
+    @property
+    def response_timeout_seconds(self):
+        """Gets the response_timeout_seconds of this Task.  # noqa: E501
+
+
+        :return: The response_timeout_seconds of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._response_timeout_seconds
+
+    @response_timeout_seconds.setter
+    def response_timeout_seconds(self, response_timeout_seconds):
+        """Sets the response_timeout_seconds of this Task.
+
+
+        :param response_timeout_seconds: The response_timeout_seconds of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._response_timeout_seconds = response_timeout_seconds
+
+    @property
+    def workflow_instance_id(self):
+        """Gets the workflow_instance_id of this Task.  # noqa: E501
+
+
+        :return: The workflow_instance_id of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_instance_id
+
+    @workflow_instance_id.setter
+    def workflow_instance_id(self, workflow_instance_id):
+        """Sets the workflow_instance_id of this Task.
+
+
+        :param workflow_instance_id: The workflow_instance_id of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_instance_id = workflow_instance_id
+
+    @property
+    def workflow_type(self):
+        """Gets the workflow_type of this Task.  # noqa: E501
+
+
+        :return: The workflow_type of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_type
+
+    @workflow_type.setter
+    def workflow_type(self, workflow_type):
+        """Sets the workflow_type of this Task.
+
+
+        :param workflow_type: The workflow_type of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_type = workflow_type
+
+    @property
+    def task_id(self):
+        """Gets the task_id of this Task.  # noqa: E501
+
+
+        :return: The task_id of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_id
+
+    @task_id.setter
+    def task_id(self, task_id):
+        """Sets the task_id of this Task.
+
+
+        :param task_id: The task_id of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._task_id = task_id
+
+    @property
+    def reason_for_incompletion(self):
+        """Gets the reason_for_incompletion of this Task.  # noqa: E501
+
+
+        :return: The reason_for_incompletion of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._reason_for_incompletion
+
+    @reason_for_incompletion.setter
+    def reason_for_incompletion(self, reason_for_incompletion):
+        """Sets the reason_for_incompletion of this Task.
+
+
+        :param reason_for_incompletion: The reason_for_incompletion of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._reason_for_incompletion = reason_for_incompletion
+
+    @property
+    def callback_after_seconds(self):
+        """Gets the callback_after_seconds of this Task.  # noqa: E501
+
+
+        :return: The callback_after_seconds of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._callback_after_seconds
+
+    @callback_after_seconds.setter
+    def callback_after_seconds(self, callback_after_seconds):
+        """Sets the callback_after_seconds of this Task.
+
+
+        :param callback_after_seconds: The callback_after_seconds of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._callback_after_seconds = callback_after_seconds
+
+    @property
+    def worker_id(self):
+        """Gets the worker_id of this Task.  # noqa: E501
+
+
+        :return: The worker_id of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._worker_id
+
+    @worker_id.setter
+    def worker_id(self, worker_id):
+        """Sets the worker_id of this Task.
+
+
+        :param worker_id: The worker_id of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._worker_id = worker_id
+
+    @property
+    def output_data(self):
+        """Gets the output_data of this Task.  # noqa: E501
+
+
+        :return: The output_data of this Task.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._output_data
+
+    @output_data.setter
+    def output_data(self, output_data):
+        """Sets the output_data of this Task.
+
+
+        :param output_data: The output_data of this Task.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._output_data = output_data
+
+    @property
+    def workflow_task(self) -> WorkflowTask:
+        """Gets the workflow_task of this Task.  # noqa: E501
+
+
+        :return: The workflow_task of this Task.  # noqa: E501
+        :rtype: WorkflowTask
+        """
+        return self._workflow_task
+
+    @workflow_task.setter
+    def workflow_task(self, workflow_task):
+        """Sets the workflow_task of this Task.
+
+
+        :param workflow_task: The workflow_task of this Task.  # noqa: E501
+        :type: WorkflowTask
+        """
+
+        self._workflow_task = workflow_task
+
+    @property
+    def domain(self):
+        """Gets the domain of this Task.  # noqa: E501
+
+
+        :return: The domain of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._domain
+
+    @domain.setter
+    def domain(self, domain):
+        """Sets the domain of this Task.
+
+
+        :param domain: The domain of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._domain = domain
+
+    @property
+    def rate_limit_per_frequency(self):
+        """Gets the rate_limit_per_frequency of this Task.  # noqa: E501
+
+
+        :return: The rate_limit_per_frequency of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._rate_limit_per_frequency
+
+    @rate_limit_per_frequency.setter
+    def rate_limit_per_frequency(self, rate_limit_per_frequency):
+        """Sets the rate_limit_per_frequency of this Task.
+
+
+        :param rate_limit_per_frequency: The rate_limit_per_frequency of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._rate_limit_per_frequency = rate_limit_per_frequency
+
+    @property
+    def rate_limit_frequency_in_seconds(self):
+        """Gets the rate_limit_frequency_in_seconds of this Task.  # noqa: E501
+
+
+        :return: The rate_limit_frequency_in_seconds of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._rate_limit_frequency_in_seconds
+
+    @rate_limit_frequency_in_seconds.setter
+    def rate_limit_frequency_in_seconds(self, rate_limit_frequency_in_seconds):
+        """Sets the rate_limit_frequency_in_seconds of this Task.
+
+
+        :param rate_limit_frequency_in_seconds: The rate_limit_frequency_in_seconds of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._rate_limit_frequency_in_seconds = rate_limit_frequency_in_seconds
+
+    @property
+    def external_input_payload_storage_path(self):
+        """Gets the external_input_payload_storage_path of this Task.  # noqa: E501
+
+
+        :return: The external_input_payload_storage_path of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_input_payload_storage_path
+
+    @external_input_payload_storage_path.setter
+    def external_input_payload_storage_path(self, external_input_payload_storage_path):
+        """Sets the external_input_payload_storage_path of this Task.
+
+
+        :param external_input_payload_storage_path: The external_input_payload_storage_path of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._external_input_payload_storage_path = external_input_payload_storage_path
+
+    @property
+    def external_output_payload_storage_path(self):
+        """Gets the external_output_payload_storage_path of this Task.  # noqa: E501
+
+
+        :return: The external_output_payload_storage_path of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_output_payload_storage_path
+
+    @external_output_payload_storage_path.setter
+    def external_output_payload_storage_path(
+        self, external_output_payload_storage_path
+    ):
+        """Sets the external_output_payload_storage_path of this Task.
+
+
+        :param external_output_payload_storage_path: The external_output_payload_storage_path of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._external_output_payload_storage_path = (
+            external_output_payload_storage_path
+        )
+
+    @property
+    def workflow_priority(self):
+        """Gets the workflow_priority of this Task.  # noqa: E501
+
+
+        :return: The workflow_priority of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._workflow_priority
+
+    @workflow_priority.setter
+    def workflow_priority(self, workflow_priority):
+        """Sets the workflow_priority of this Task.
+
+
+        :param workflow_priority: The workflow_priority of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._workflow_priority = workflow_priority
+
+    @property
+    def execution_name_space(self):
+        """Gets the execution_name_space of this Task.  # noqa: E501
+
+
+        :return: The execution_name_space of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._execution_name_space
+
+    @execution_name_space.setter
+    def execution_name_space(self, execution_name_space):
+        """Sets the execution_name_space of this Task.
+
+
+        :param execution_name_space: The execution_name_space of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._execution_name_space = execution_name_space
+
+    @property
+    def isolation_group_id(self):
+        """Gets the isolation_group_id of this Task.  # noqa: E501
+
+
+        :return: The isolation_group_id of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._isolation_group_id
+
+    @isolation_group_id.setter
+    def isolation_group_id(self, isolation_group_id):
+        """Sets the isolation_group_id of this Task.
+
+
+        :param isolation_group_id: The isolation_group_id of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._isolation_group_id = isolation_group_id
+
+    @property
+    def iteration(self):
+        """Gets the iteration of this Task.  # noqa: E501
+
+
+        :return: The iteration of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._iteration
+
+    @iteration.setter
+    def iteration(self, iteration):
+        """Sets the iteration of this Task.
+
+
+        :param iteration: The iteration of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._iteration = iteration
+
+    @property
+    def sub_workflow_id(self):
+        """Gets the sub_workflow_id of this Task.  # noqa: E501
+
+
+        :return: The sub_workflow_id of this Task.  # noqa: E501
+        :rtype: str
+        """
+        return self._sub_workflow_id
+
+    @sub_workflow_id.setter
+    def sub_workflow_id(self, sub_workflow_id):
+        """Sets the sub_workflow_id of this Task.
+
+
+        :param sub_workflow_id: The sub_workflow_id of this Task.  # noqa: E501
+        :type: str
+        """
+
+        self._sub_workflow_id = sub_workflow_id
+
+    @property
+    def subworkflow_changed(self):
+        """Gets the subworkflow_changed of this Task.  # noqa: E501
+
+
+        :return: The subworkflow_changed of this Task.  # noqa: E501
+        :rtype: bool
+        """
+        return self._subworkflow_changed
+
+    @subworkflow_changed.setter
+    def subworkflow_changed(self, subworkflow_changed):
+        """Sets the subworkflow_changed of this Task.
+
+
+        :param subworkflow_changed: The subworkflow_changed of this Task.  # noqa: E501
+        :type: bool
+        """
+
+        self._subworkflow_changed = subworkflow_changed
+
+    @property
+    def loop_over_task(self):
+        """Gets the loop_over_task of this Task.  # noqa: E501
+
+
+        :return: The loop_over_task of this Task.  # noqa: E501
+        :rtype: bool
+        """
+        return self._loop_over_task
+
+    @loop_over_task.setter
+    def loop_over_task(self, loop_over_task):
+        """Sets the loop_over_task of this Task.
+
+
+        :param loop_over_task: The loop_over_task of this Task.  # noqa: E501
+        :type: bool
+        """
+
+        self._loop_over_task = loop_over_task
+
+    @property
+    def task_definition(self):
+        """Gets the task_definition of this Task.  # noqa: E501
+
+
+        :return: The task_definition of this Task.  # noqa: E501
+        :rtype: TaskDef
+        """
+        return self._task_definition
+
+    @task_definition.setter
+    def task_definition(self, task_definition):
+        """Sets the task_definition of this Task.
+
+
+        :param task_definition: The task_definition of this Task.  # noqa: E501
+        :type: TaskDef
+        """
+
+        self._task_definition = task_definition
+
+    @property
+    def queue_wait_time(self):
+        """Gets the queue_wait_time of this Task.  # noqa: E501
+
+
+        :return: The queue_wait_time of this Task.  # noqa: E501
+        :rtype: int
+        """
+        return self._queue_wait_time
+
+    @queue_wait_time.setter
+    def queue_wait_time(self, queue_wait_time):
+        """Sets the queue_wait_time of this Task.
+
+
+        :param queue_wait_time: The queue_wait_time of this Task.  # noqa: E501
+        :type: int
+        """
+
+        self._queue_wait_time = queue_wait_time
+
+    @property
+    def biz_meta(self):
+        return self._biz_meta
+
+    @biz_meta.setter
+    def biz_meta(self, biz_meta):
+        self._biz_meta = biz_meta
+
+    @property
+    def callback_url(self):
+        return self._callback_url
+
+    @callback_url.setter
+    def callback_url(self, callback_url):
+        self._callback_url = callback_url
+    
+    @property
+    def conversation_info(self):
+        return self._conversation_info
+    
+    @conversation_info.setter
+    def conversation_info(self, conversation_info):
+        self._conversation_info = conversation_info
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Task, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Task):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
+
+    def to_task_result(
+        self, status: TaskResultStatus = TaskResultStatus.COMPLETED
+    ) -> TaskResult:
+        task_result = TaskResult(
+            task_id=self.task_id,
+            workflow_instance_id=self.workflow_instance_id,
+            worker_id=self.worker_id,
+            status=status,
+        )
+        return task_result
diff --git a/omagent_core/engine/http/models/task_def.py b/omagent_core/engine/http/models/task_def.py
new file mode 100644
index 0000000000000000000000000000000000000000..f2f0269d4a8646a864a5e4fd93ecb3e26d0ba9ce
--- /dev/null
+++ b/omagent_core/engine/http/models/task_def.py
@@ -0,0 +1,742 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class TaskDef(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "owner_app": "str",
+        "create_time": "int",
+        "update_time": "int",
+        "created_by": "str",
+        "updated_by": "str",
+        "name": "str",
+        "description": "str",
+        "retry_count": "int",
+        "timeout_seconds": "int",
+        "input_keys": "list[str]",
+        "output_keys": "list[str]",
+        "timeout_policy": "str",
+        "retry_logic": "str",
+        "retry_delay_seconds": "int",
+        "response_timeout_seconds": "int",
+        "concurrent_exec_limit": "int",
+        "input_template": "dict(str, object)",
+        "rate_limit_per_frequency": "int",
+        "rate_limit_frequency_in_seconds": "int",
+        "isolation_group_id": "str",
+        "execution_name_space": "str",
+        "owner_email": "str",
+        "poll_timeout_seconds": "int",
+        "backoff_scale_factor": "int",
+    }
+
+    attribute_map = {
+        "owner_app": "ownerApp",
+        "create_time": "createTime",
+        "update_time": "updateTime",
+        "created_by": "createdBy",
+        "updated_by": "updatedBy",
+        "name": "name",
+        "description": "description",
+        "retry_count": "retryCount",
+        "timeout_seconds": "timeoutSeconds",
+        "input_keys": "inputKeys",
+        "output_keys": "outputKeys",
+        "timeout_policy": "timeoutPolicy",
+        "retry_logic": "retryLogic",
+        "retry_delay_seconds": "retryDelaySeconds",
+        "response_timeout_seconds": "responseTimeoutSeconds",
+        "concurrent_exec_limit": "concurrentExecLimit",
+        "input_template": "inputTemplate",
+        "rate_limit_per_frequency": "rateLimitPerFrequency",
+        "rate_limit_frequency_in_seconds": "rateLimitFrequencyInSeconds",
+        "isolation_group_id": "isolationGroupId",
+        "execution_name_space": "executionNameSpace",
+        "owner_email": "ownerEmail",
+        "poll_timeout_seconds": "pollTimeoutSeconds",
+        "backoff_scale_factor": "backoffScaleFactor",
+    }
+
+    def __init__(
+        self,
+        owner_app=None,
+        create_time=None,
+        update_time=None,
+        created_by=None,
+        updated_by=None,
+        name=None,
+        description=None,
+        retry_count=None,
+        timeout_seconds=None,
+        input_keys=None,
+        output_keys=None,
+        timeout_policy=None,
+        retry_logic=None,
+        retry_delay_seconds=None,
+        response_timeout_seconds=None,
+        concurrent_exec_limit=None,
+        input_template=None,
+        rate_limit_per_frequency=None,
+        rate_limit_frequency_in_seconds=None,
+        isolation_group_id=None,
+        execution_name_space=None,
+        owner_email=None,
+        poll_timeout_seconds=None,
+        backoff_scale_factor=None,
+    ):  # noqa: E501
+        """TaskDef - a model defined in Swagger"""  # noqa: E501
+        self._owner_app = None
+        self._create_time = None
+        self._update_time = None
+        self._created_by = None
+        self._updated_by = None
+        self._name = None
+        self._description = None
+        self._retry_count = None
+        self._timeout_seconds = None
+        self._input_keys = None
+        self._output_keys = None
+        self._timeout_policy = None
+        self._retry_logic = None
+        self._retry_delay_seconds = None
+        self._response_timeout_seconds = None
+        self._concurrent_exec_limit = None
+        self._input_template = None
+        self._rate_limit_per_frequency = None
+        self._rate_limit_frequency_in_seconds = None
+        self._isolation_group_id = None
+        self._execution_name_space = None
+        self._owner_email = None
+        self._poll_timeout_seconds = None
+        self._backoff_scale_factor = None
+        self.discriminator = None
+        if owner_app is not None:
+            self.owner_app = owner_app
+        if create_time is not None:
+            self.create_time = create_time
+        if update_time is not None:
+            self.update_time = update_time
+        if created_by is not None:
+            self.created_by = created_by
+        if updated_by is not None:
+            self.updated_by = updated_by
+        self.name = name
+        if description is not None:
+            self.description = description
+        if retry_count is not None:
+            self.retry_count = retry_count
+        self.timeout_seconds = timeout_seconds
+        if input_keys is not None:
+            self.input_keys = input_keys
+        if output_keys is not None:
+            self.output_keys = output_keys
+        if timeout_policy is not None:
+            self.timeout_policy = timeout_policy
+        if retry_logic is not None:
+            self.retry_logic = retry_logic
+        if retry_delay_seconds is not None:
+            self.retry_delay_seconds = retry_delay_seconds
+        if response_timeout_seconds is not None:
+            self.response_timeout_seconds = response_timeout_seconds
+        if concurrent_exec_limit is not None:
+            self.concurrent_exec_limit = concurrent_exec_limit
+        if input_template is not None:
+            self.input_template = input_template
+        if rate_limit_per_frequency is not None:
+            self.rate_limit_per_frequency = rate_limit_per_frequency
+        if rate_limit_frequency_in_seconds is not None:
+            self.rate_limit_frequency_in_seconds = rate_limit_frequency_in_seconds
+        if isolation_group_id is not None:
+            self.isolation_group_id = isolation_group_id
+        if execution_name_space is not None:
+            self.execution_name_space = execution_name_space
+        if owner_email is not None:
+            self.owner_email = owner_email
+        if poll_timeout_seconds is not None:
+            self.poll_timeout_seconds = poll_timeout_seconds
+        if backoff_scale_factor is not None:
+            self.backoff_scale_factor = backoff_scale_factor
+
+    @property
+    def owner_app(self):
+        """Gets the owner_app of this TaskDef.  # noqa: E501
+
+
+        :return: The owner_app of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._owner_app
+
+    @owner_app.setter
+    def owner_app(self, owner_app):
+        """Sets the owner_app of this TaskDef.
+
+
+        :param owner_app: The owner_app of this TaskDef.  # noqa: E501
+        :type: str
+        """
+
+        self._owner_app = owner_app
+
+    @property
+    def create_time(self):
+        """Gets the create_time of this TaskDef.  # noqa: E501
+
+
+        :return: The create_time of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._create_time
+
+    @create_time.setter
+    def create_time(self, create_time):
+        """Sets the create_time of this TaskDef.
+
+
+        :param create_time: The create_time of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._create_time = create_time
+
+    @property
+    def update_time(self):
+        """Gets the update_time of this TaskDef.  # noqa: E501
+
+
+        :return: The update_time of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._update_time
+
+    @update_time.setter
+    def update_time(self, update_time):
+        """Sets the update_time of this TaskDef.
+
+
+        :param update_time: The update_time of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._update_time = update_time
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this TaskDef.  # noqa: E501
+
+
+        :return: The created_by of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this TaskDef.
+
+
+        :param created_by: The created_by of this TaskDef.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this TaskDef.  # noqa: E501
+
+
+        :return: The updated_by of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this TaskDef.
+
+
+        :param updated_by: The updated_by of this TaskDef.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    @property
+    def name(self):
+        """Gets the name of this TaskDef.  # noqa: E501
+
+
+        :return: The name of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this TaskDef.
+
+
+        :param name: The name of this TaskDef.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def description(self):
+        """Gets the description of this TaskDef.  # noqa: E501
+
+
+        :return: The description of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this TaskDef.
+
+
+        :param description: The description of this TaskDef.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def retry_count(self):
+        """Gets the retry_count of this TaskDef.  # noqa: E501
+
+
+        :return: The retry_count of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._retry_count
+
+    @retry_count.setter
+    def retry_count(self, retry_count):
+        """Sets the retry_count of this TaskDef.
+
+
+        :param retry_count: The retry_count of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._retry_count = retry_count
+
+    @property
+    def timeout_seconds(self):
+        """Gets the timeout_seconds of this TaskDef.  # noqa: E501
+
+
+        :return: The timeout_seconds of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._timeout_seconds
+
+    @timeout_seconds.setter
+    def timeout_seconds(self, timeout_seconds):
+        """Sets the timeout_seconds of this TaskDef.
+
+
+        :param timeout_seconds: The timeout_seconds of this TaskDef.  # noqa: E501
+        :type: int
+        """
+        self._timeout_seconds = timeout_seconds
+
+    @property
+    def input_keys(self):
+        """Gets the input_keys of this TaskDef.  # noqa: E501
+
+
+        :return: The input_keys of this TaskDef.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._input_keys
+
+    @input_keys.setter
+    def input_keys(self, input_keys):
+        """Sets the input_keys of this TaskDef.
+
+
+        :param input_keys: The input_keys of this TaskDef.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._input_keys = input_keys
+
+    @property
+    def output_keys(self):
+        """Gets the output_keys of this TaskDef.  # noqa: E501
+
+
+        :return: The output_keys of this TaskDef.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._output_keys
+
+    @output_keys.setter
+    def output_keys(self, output_keys):
+        """Sets the output_keys of this TaskDef.
+
+
+        :param output_keys: The output_keys of this TaskDef.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._output_keys = output_keys
+
+    @property
+    def timeout_policy(self):
+        """Gets the timeout_policy of this TaskDef.  # noqa: E501
+
+
+        :return: The timeout_policy of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._timeout_policy
+
+    @timeout_policy.setter
+    def timeout_policy(self, timeout_policy):
+        """Sets the timeout_policy of this TaskDef.
+
+
+        :param timeout_policy: The timeout_policy of this TaskDef.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["RETRY", "TIME_OUT_WF", "ALERT_ONLY"]  # noqa: E501
+        if timeout_policy not in allowed_values:
+            raise ValueError(
+                "Invalid value for `timeout_policy` ({0}), must be one of {1}".format(  # noqa: E501
+                    timeout_policy, allowed_values
+                )
+            )
+
+        self._timeout_policy = timeout_policy
+
+    @property
+    def retry_logic(self):
+        """Gets the retry_logic of this TaskDef.  # noqa: E501
+
+
+        :return: The retry_logic of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._retry_logic
+
+    @retry_logic.setter
+    def retry_logic(self, retry_logic):
+        """Sets the retry_logic of this TaskDef.
+
+
+        :param retry_logic: The retry_logic of this TaskDef.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            "FIXED",
+            "EXPONENTIAL_BACKOFF",
+            "LINEAR_BACKOFF",
+        ]  # noqa: E501
+        if retry_logic not in allowed_values:
+            raise ValueError(
+                "Invalid value for `retry_logic` ({0}), must be one of {1}".format(  # noqa: E501
+                    retry_logic, allowed_values
+                )
+            )
+
+        self._retry_logic = retry_logic
+
+    @property
+    def retry_delay_seconds(self):
+        """Gets the retry_delay_seconds of this TaskDef.  # noqa: E501
+
+
+        :return: The retry_delay_seconds of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._retry_delay_seconds
+
+    @retry_delay_seconds.setter
+    def retry_delay_seconds(self, retry_delay_seconds):
+        """Sets the retry_delay_seconds of this TaskDef.
+
+
+        :param retry_delay_seconds: The retry_delay_seconds of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._retry_delay_seconds = retry_delay_seconds
+
+    @property
+    def response_timeout_seconds(self):
+        """Gets the response_timeout_seconds of this TaskDef.  # noqa: E501
+
+
+        :return: The response_timeout_seconds of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._response_timeout_seconds
+
+    @response_timeout_seconds.setter
+    def response_timeout_seconds(self, response_timeout_seconds):
+        """Sets the response_timeout_seconds of this TaskDef.
+
+
+        :param response_timeout_seconds: The response_timeout_seconds of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._response_timeout_seconds = response_timeout_seconds
+
+    @property
+    def concurrent_exec_limit(self):
+        """Gets the concurrent_exec_limit of this TaskDef.  # noqa: E501
+
+
+        :return: The concurrent_exec_limit of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._concurrent_exec_limit
+
+    @concurrent_exec_limit.setter
+    def concurrent_exec_limit(self, concurrent_exec_limit):
+        """Sets the concurrent_exec_limit of this TaskDef.
+
+
+        :param concurrent_exec_limit: The concurrent_exec_limit of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._concurrent_exec_limit = concurrent_exec_limit
+
+    @property
+    def input_template(self):
+        """Gets the input_template of this TaskDef.  # noqa: E501
+
+
+        :return: The input_template of this TaskDef.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input_template
+
+    @input_template.setter
+    def input_template(self, input_template):
+        """Sets the input_template of this TaskDef.
+
+
+        :param input_template: The input_template of this TaskDef.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input_template = input_template
+
+    @property
+    def rate_limit_per_frequency(self):
+        """Gets the rate_limit_per_frequency of this TaskDef.  # noqa: E501
+
+
+        :return: The rate_limit_per_frequency of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._rate_limit_per_frequency
+
+    @rate_limit_per_frequency.setter
+    def rate_limit_per_frequency(self, rate_limit_per_frequency):
+        """Sets the rate_limit_per_frequency of this TaskDef.
+
+
+        :param rate_limit_per_frequency: The rate_limit_per_frequency of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._rate_limit_per_frequency = rate_limit_per_frequency
+
+    @property
+    def rate_limit_frequency_in_seconds(self):
+        """Gets the rate_limit_frequency_in_seconds of this TaskDef.  # noqa: E501
+
+
+        :return: The rate_limit_frequency_in_seconds of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._rate_limit_frequency_in_seconds
+
+    @rate_limit_frequency_in_seconds.setter
+    def rate_limit_frequency_in_seconds(self, rate_limit_frequency_in_seconds):
+        """Sets the rate_limit_frequency_in_seconds of this TaskDef.
+
+
+        :param rate_limit_frequency_in_seconds: The rate_limit_frequency_in_seconds of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._rate_limit_frequency_in_seconds = rate_limit_frequency_in_seconds
+
+    @property
+    def isolation_group_id(self):
+        """Gets the isolation_group_id of this TaskDef.  # noqa: E501
+
+
+        :return: The isolation_group_id of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._isolation_group_id
+
+    @isolation_group_id.setter
+    def isolation_group_id(self, isolation_group_id):
+        """Sets the isolation_group_id of this TaskDef.
+
+
+        :param isolation_group_id: The isolation_group_id of this TaskDef.  # noqa: E501
+        :type: str
+        """
+
+        self._isolation_group_id = isolation_group_id
+
+    @property
+    def execution_name_space(self):
+        """Gets the execution_name_space of this TaskDef.  # noqa: E501
+
+
+        :return: The execution_name_space of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._execution_name_space
+
+    @execution_name_space.setter
+    def execution_name_space(self, execution_name_space):
+        """Sets the execution_name_space of this TaskDef.
+
+
+        :param execution_name_space: The execution_name_space of this TaskDef.  # noqa: E501
+        :type: str
+        """
+
+        self._execution_name_space = execution_name_space
+
+    @property
+    def owner_email(self):
+        """Gets the owner_email of this TaskDef.  # noqa: E501
+
+
+        :return: The owner_email of this TaskDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._owner_email
+
+    @owner_email.setter
+    def owner_email(self, owner_email):
+        """Sets the owner_email of this TaskDef.
+
+
+        :param owner_email: The owner_email of this TaskDef.  # noqa: E501
+        :type: str
+        """
+
+        self._owner_email = owner_email
+
+    @property
+    def poll_timeout_seconds(self):
+        """Gets the poll_timeout_seconds of this TaskDef.  # noqa: E501
+
+
+        :return: The poll_timeout_seconds of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._poll_timeout_seconds
+
+    @poll_timeout_seconds.setter
+    def poll_timeout_seconds(self, poll_timeout_seconds):
+        """Sets the poll_timeout_seconds of this TaskDef.
+
+
+        :param poll_timeout_seconds: The poll_timeout_seconds of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._poll_timeout_seconds = poll_timeout_seconds
+
+    @property
+    def backoff_scale_factor(self):
+        """Gets the backoff_scale_factor of this TaskDef.  # noqa: E501
+
+
+        :return: The backoff_scale_factor of this TaskDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._backoff_scale_factor
+
+    @backoff_scale_factor.setter
+    def backoff_scale_factor(self, backoff_scale_factor):
+        """Sets the backoff_scale_factor of this TaskDef.
+
+
+        :param backoff_scale_factor: The backoff_scale_factor of this TaskDef.  # noqa: E501
+        :type: int
+        """
+
+        self._backoff_scale_factor = backoff_scale_factor
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TaskDef, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TaskDef):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/task_details.py b/omagent_core/engine/http/models/task_details.py
new file mode 100644
index 0000000000000000000000000000000000000000..39c77548b57739862d2f0e05ef8b0acb9fd3d34c
--- /dev/null
+++ b/omagent_core/engine/http/models/task_details.py
@@ -0,0 +1,184 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class TaskDetails(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "workflow_id": "str",
+        "task_ref_name": "str",
+        "output": "dict(str, object)",
+        "task_id": "str",
+    }
+
+    attribute_map = {
+        "workflow_id": "workflowId",
+        "task_ref_name": "taskRefName",
+        "output": "output",
+        "task_id": "taskId",
+    }
+
+    def __init__(
+        self, workflow_id=None, task_ref_name=None, output=None, task_id=None
+    ):  # noqa: E501
+        """TaskDetails - a model defined in Swagger"""  # noqa: E501
+        self._workflow_id = None
+        self._task_ref_name = None
+        self._output = None
+        self._task_id = None
+        self.discriminator = None
+        if workflow_id is not None:
+            self.workflow_id = workflow_id
+        if task_ref_name is not None:
+            self.task_ref_name = task_ref_name
+        if output is not None:
+            self.output = output
+        if task_id is not None:
+            self.task_id = task_id
+
+    @property
+    def workflow_id(self):
+        """Gets the workflow_id of this TaskDetails.  # noqa: E501
+
+
+        :return: The workflow_id of this TaskDetails.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_id
+
+    @workflow_id.setter
+    def workflow_id(self, workflow_id):
+        """Sets the workflow_id of this TaskDetails.
+
+
+        :param workflow_id: The workflow_id of this TaskDetails.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_id = workflow_id
+
+    @property
+    def task_ref_name(self):
+        """Gets the task_ref_name of this TaskDetails.  # noqa: E501
+
+
+        :return: The task_ref_name of this TaskDetails.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_ref_name
+
+    @task_ref_name.setter
+    def task_ref_name(self, task_ref_name):
+        """Sets the task_ref_name of this TaskDetails.
+
+
+        :param task_ref_name: The task_ref_name of this TaskDetails.  # noqa: E501
+        :type: str
+        """
+
+        self._task_ref_name = task_ref_name
+
+    @property
+    def output(self):
+        """Gets the output of this TaskDetails.  # noqa: E501
+
+
+        :return: The output of this TaskDetails.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._output
+
+    @output.setter
+    def output(self, output):
+        """Sets the output of this TaskDetails.
+
+
+        :param output: The output of this TaskDetails.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._output = output
+
+    @property
+    def task_id(self):
+        """Gets the task_id of this TaskDetails.  # noqa: E501
+
+
+        :return: The task_id of this TaskDetails.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_id
+
+    @task_id.setter
+    def task_id(self, task_id):
+        """Sets the task_id of this TaskDetails.
+
+
+        :param task_id: The task_id of this TaskDetails.  # noqa: E501
+        :type: str
+        """
+
+        self._task_id = task_id
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TaskDetails, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TaskDetails):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/task_exec_log.py b/omagent_core/engine/http/models/task_exec_log.py
new file mode 100644
index 0000000000000000000000000000000000000000..371034208b7d25811c2060a3ce9eae46599244e6
--- /dev/null
+++ b/omagent_core/engine/http/models/task_exec_log.py
@@ -0,0 +1,148 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class TaskExecLog(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"log": "str", "task_id": "str", "created_time": "int"}
+
+    attribute_map = {"log": "log", "task_id": "taskId", "created_time": "createdTime"}
+
+    def __init__(self, log=None, task_id=None, created_time=None):  # noqa: E501
+        """TaskExecLog - a model defined in Swagger"""  # noqa: E501
+        self._log = None
+        self._task_id = None
+        self._created_time = None
+        self.discriminator = None
+        if log is not None:
+            self.log = log
+        if task_id is not None:
+            self.task_id = task_id
+        if created_time is not None:
+            self.created_time = created_time
+
+    @property
+    def log(self):
+        """Gets the log of this TaskExecLog.  # noqa: E501
+
+
+        :return: The log of this TaskExecLog.  # noqa: E501
+        :rtype: str
+        """
+        return self._log
+
+    @log.setter
+    def log(self, log):
+        """Sets the log of this TaskExecLog.
+
+
+        :param log: The log of this TaskExecLog.  # noqa: E501
+        :type: str
+        """
+
+        self._log = log
+
+    @property
+    def task_id(self):
+        """Gets the task_id of this TaskExecLog.  # noqa: E501
+
+
+        :return: The task_id of this TaskExecLog.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_id
+
+    @task_id.setter
+    def task_id(self, task_id):
+        """Sets the task_id of this TaskExecLog.
+
+
+        :param task_id: The task_id of this TaskExecLog.  # noqa: E501
+        :type: str
+        """
+
+        self._task_id = task_id
+
+    @property
+    def created_time(self):
+        """Gets the created_time of this TaskExecLog.  # noqa: E501
+
+
+        :return: The created_time of this TaskExecLog.  # noqa: E501
+        :rtype: int
+        """
+        return self._created_time
+
+    @created_time.setter
+    def created_time(self, created_time):
+        """Sets the created_time of this TaskExecLog.
+
+
+        :param created_time: The created_time of this TaskExecLog.  # noqa: E501
+        :type: int
+        """
+
+        self._created_time = created_time
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TaskExecLog, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TaskExecLog):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/task_result.py b/omagent_core/engine/http/models/task_result.py
new file mode 100644
index 0000000000000000000000000000000000000000..f14139f8a2fbba65d4ab3796548afa3b8057d678
--- /dev/null
+++ b/omagent_core/engine/http/models/task_result.py
@@ -0,0 +1,391 @@
+import pprint
+import re  # noqa: F401
+
+import six
+from omagent_core.engine.http.models.task_result_status import TaskResultStatus
+
+
+class TaskResult(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "workflow_instance_id": "str",
+        "task_id": "str",
+        "reason_for_incompletion": "str",
+        "callback_after_seconds": "int",
+        "worker_id": "str",
+        "status": "str",
+        "output_data": "dict(str, object)",
+        "logs": "list[TaskExecLog]",
+        "external_output_payload_storage_path": "str",
+        "sub_workflow_id": "str",
+        "biz_meta": "str",
+        "callback_url": "str",
+    }
+
+    attribute_map = {
+        "workflow_instance_id": "workflowInstanceId",
+        "task_id": "taskId",
+        "reason_for_incompletion": "reasonForIncompletion",
+        "callback_after_seconds": "callbackAfterSeconds",
+        "worker_id": "workerId",
+        "status": "status",
+        "output_data": "outputData",
+        "logs": "logs",
+        "external_output_payload_storage_path": "externalOutputPayloadStoragePath",
+        "sub_workflow_id": "subWorkflowId",
+        "biz_meta": "bizMeta",
+        "callback_url": "callbackUrl",
+    }
+
+    def __init__(
+        self,
+        workflow_instance_id=None,
+        task_id=None,
+        reason_for_incompletion=None,
+        callback_after_seconds=None,
+        worker_id=None,
+        status=None,
+        output_data=None,
+        logs=None,
+        external_output_payload_storage_path=None,
+        sub_workflow_id=None,
+        biz_meta=None,
+        callback_url=None,
+    ):  # noqa: E501
+        """TaskResult - a model defined in Swagger"""  # noqa: E501
+        self._workflow_instance_id = None
+        self._task_id = None
+        self._reason_for_incompletion = None
+        self._callback_after_seconds = None
+        self._worker_id = None
+        self._status = None
+        self._output_data = None
+        self._logs = None
+        self._external_output_payload_storage_path = None
+        self._sub_workflow_id = None
+        self.discriminator = None
+        self.workflow_instance_id = workflow_instance_id
+        self.task_id = task_id
+        self.biz_meta = biz_meta
+        self.callback_url = callback_url
+        if reason_for_incompletion is not None:
+            self.reason_for_incompletion = reason_for_incompletion
+        if callback_after_seconds is not None:
+            self.callback_after_seconds = callback_after_seconds
+        if worker_id is not None:
+            self.worker_id = worker_id
+        if status is not None:
+            self.status = status
+        if output_data is not None:
+            self.output_data = output_data
+        if logs is not None:
+            self.logs = logs
+        if external_output_payload_storage_path is not None:
+            self.external_output_payload_storage_path = (
+                external_output_payload_storage_path
+            )
+        if sub_workflow_id is not None:
+            self.sub_workflow_id = sub_workflow_id
+
+    @property
+    def workflow_instance_id(self):
+        """Gets the workflow_instance_id of this TaskResult.  # noqa: E501
+
+
+        :return: The workflow_instance_id of this TaskResult.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_instance_id
+
+    @workflow_instance_id.setter
+    def workflow_instance_id(self, workflow_instance_id):
+        """Sets the workflow_instance_id of this TaskResult.
+
+
+        :param workflow_instance_id: The workflow_instance_id of this TaskResult.  # noqa: E501
+        :type: str
+        """
+        self._workflow_instance_id = workflow_instance_id
+
+    @property
+    def task_id(self):
+        """Gets the task_id of this TaskResult.  # noqa: E501
+
+
+        :return: The task_id of this TaskResult.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_id
+
+    @task_id.setter
+    def task_id(self, task_id):
+        """Sets the task_id of this TaskResult.
+
+
+        :param task_id: The task_id of this TaskResult.  # noqa: E501
+        :type: str
+        """
+        self._task_id = task_id
+
+    @property
+    def reason_for_incompletion(self):
+        """Gets the reason_for_incompletion of this TaskResult.  # noqa: E501
+
+
+        :return: The reason_for_incompletion of this TaskResult.  # noqa: E501
+        :rtype: str
+        """
+        return self._reason_for_incompletion
+
+    @reason_for_incompletion.setter
+    def reason_for_incompletion(self, reason_for_incompletion):
+        """Sets the reason_for_incompletion of this TaskResult.
+
+
+        :param reason_for_incompletion: The reason_for_incompletion of this TaskResult.  # noqa: E501
+        :type: str
+        """
+
+        self._reason_for_incompletion = reason_for_incompletion
+
+    @property
+    def callback_after_seconds(self):
+        """Gets the callback_after_seconds of this TaskResult.  # noqa: E501
+
+
+        :return: The callback_after_seconds of this TaskResult.  # noqa: E501
+        :rtype: int
+        """
+        return self._callback_after_seconds
+
+    @callback_after_seconds.setter
+    def callback_after_seconds(self, callback_after_seconds):
+        """Sets the callback_after_seconds of this TaskResult.
+
+
+        :param callback_after_seconds: The callback_after_seconds of this TaskResult.  # noqa: E501
+        :type: int
+        """
+
+        self._callback_after_seconds = callback_after_seconds
+
+    @property
+    def worker_id(self):
+        """Gets the worker_id of this TaskResult.  # noqa: E501
+
+
+        :return: The worker_id of this TaskResult.  # noqa: E501
+        :rtype: str
+        """
+        return self._worker_id
+
+    @worker_id.setter
+    def worker_id(self, worker_id):
+        """Sets the worker_id of this TaskResult.
+
+
+        :param worker_id: The worker_id of this TaskResult.  # noqa: E501
+        :type: str
+        """
+
+        self._worker_id = worker_id
+
+    @property
+    def status(self):
+        """Gets the status of this TaskResult.  # noqa: E501
+
+
+        :return: The status of this TaskResult.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    @status.setter
+    def status(self, status):
+        """Sets the status of this TaskResult.
+
+
+        :param status: The status of this TaskResult.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            task_result_status.name for task_result_status in TaskResultStatus
+        ]
+        if status not in allowed_values:
+            raise ValueError(
+                "Invalid value for `status` ({0}), must be one of {1}".format(  # noqa: E501
+                    status, allowed_values
+                )
+            )
+
+        self._status = TaskResultStatus[status]
+
+    @property
+    def output_data(self):
+        """Gets the output_data of this TaskResult.  # noqa: E501
+
+
+        :return: The output_data of this TaskResult.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._output_data
+
+    @output_data.setter
+    def output_data(self, output_data):
+        """Sets the output_data of this TaskResult.
+
+
+        :param output_data: The output_data of this TaskResult.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._output_data = output_data
+
+    @property
+    def logs(self):
+        """Gets the logs of this TaskResult.  # noqa: E501
+
+
+        :return: The logs of this TaskResult.  # noqa: E501
+        :rtype: list[TaskExecLog]
+        """
+        return self._logs
+
+    @logs.setter
+    def logs(self, logs):
+        """Sets the logs of this TaskResult.
+
+
+        :param logs: The logs of this TaskResult.  # noqa: E501
+        :type: list[TaskExecLog]
+        """
+
+        self._logs = logs
+
+    @property
+    def external_output_payload_storage_path(self):
+        """Gets the external_output_payload_storage_path of this TaskResult.  # noqa: E501
+
+
+        :return: The external_output_payload_storage_path of this TaskResult.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_output_payload_storage_path
+
+    @external_output_payload_storage_path.setter
+    def external_output_payload_storage_path(
+        self, external_output_payload_storage_path
+    ):
+        """Sets the external_output_payload_storage_path of this TaskResult.
+
+
+        :param external_output_payload_storage_path: The external_output_payload_storage_path of this TaskResult.  # noqa: E501
+        :type: str
+        """
+
+        self._external_output_payload_storage_path = (
+            external_output_payload_storage_path
+        )
+
+    @property
+    def sub_workflow_id(self):
+        """Gets the sub_workflow_id of this TaskResult.  # noqa: E501
+
+
+        :return: The sub_workflow_id of this TaskResult.  # noqa: E501
+        :rtype: str
+        """
+        return self._sub_workflow_id
+
+    @sub_workflow_id.setter
+    def sub_workflow_id(self, sub_workflow_id):
+        """Sets the sub_workflow_id of this TaskResult.
+
+
+        :param sub_workflow_id: The sub_workflow_id of this TaskResult.  # noqa: E501
+        :type: str
+        """
+
+        self._sub_workflow_id = sub_workflow_id
+
+    @property
+    def biz_meta(self):
+        return self._biz_meta
+
+    @biz_meta.setter
+    def biz_meta(self, biz_meta):
+        self._biz_meta = biz_meta
+
+    @property
+    def callback_url(self):
+        return self._callback_url
+
+    @callback_url.setter
+    def callback_url(self, callback_url):
+        self._callback_url = callback_url
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TaskResult, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TaskResult):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
+
+    def add_output_data(self, key, value):
+        if self.output_data == None:
+            self.output_data = {}
+        self.output_data[key] = value
diff --git a/omagent_core/engine/http/models/task_result_status.py b/omagent_core/engine/http/models/task_result_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6991f0e3abdf82e5c3f4a3b810cd6b9d11196a5
--- /dev/null
+++ b/omagent_core/engine/http/models/task_result_status.py
@@ -0,0 +1,11 @@
+from enum import Enum
+
+
+class TaskResultStatus(str, Enum):
+    COMPLETED = ("COMPLETED",)
+    FAILED = ("FAILED",)
+    FAILED_WITH_TERMINAL_ERROR = ("FAILED_WITH_TERMINAL_ERROR",)
+    IN_PROGRESS = "IN_PROGRESS"
+
+    def __str__(self) -> str:
+        return self.name.__str__()
diff --git a/omagent_core/engine/http/models/task_summary.py b/omagent_core/engine/http/models/task_summary.py
new file mode 100644
index 0000000000000000000000000000000000000000..d3134a227e47b9ccef0e5ee12122d8607dbd16de
--- /dev/null
+++ b/omagent_core/engine/http/models/task_summary.py
@@ -0,0 +1,618 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class TaskSummary(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "workflow_id": "str",
+        "workflow_type": "str",
+        "correlation_id": "str",
+        "scheduled_time": "str",
+        "start_time": "str",
+        "update_time": "str",
+        "end_time": "str",
+        "status": "str",
+        "reason_for_incompletion": "str",
+        "execution_time": "int",
+        "queue_wait_time": "int",
+        "task_def_name": "str",
+        "task_type": "str",
+        "input": "str",
+        "output": "str",
+        "task_id": "str",
+        "external_input_payload_storage_path": "str",
+        "external_output_payload_storage_path": "str",
+        "workflow_priority": "int",
+    }
+
+    attribute_map = {
+        "workflow_id": "workflowId",
+        "workflow_type": "workflowType",
+        "correlation_id": "correlationId",
+        "scheduled_time": "scheduledTime",
+        "start_time": "startTime",
+        "update_time": "updateTime",
+        "end_time": "endTime",
+        "status": "status",
+        "reason_for_incompletion": "reasonForIncompletion",
+        "execution_time": "executionTime",
+        "queue_wait_time": "queueWaitTime",
+        "task_def_name": "taskDefName",
+        "task_type": "taskType",
+        "input": "input",
+        "output": "output",
+        "task_id": "taskId",
+        "external_input_payload_storage_path": "externalInputPayloadStoragePath",
+        "external_output_payload_storage_path": "externalOutputPayloadStoragePath",
+        "workflow_priority": "workflowPriority",
+    }
+
+    def __init__(
+        self,
+        workflow_id=None,
+        workflow_type=None,
+        correlation_id=None,
+        scheduled_time=None,
+        start_time=None,
+        update_time=None,
+        end_time=None,
+        status=None,
+        reason_for_incompletion=None,
+        execution_time=None,
+        queue_wait_time=None,
+        task_def_name=None,
+        task_type=None,
+        input=None,
+        output=None,
+        task_id=None,
+        external_input_payload_storage_path=None,
+        external_output_payload_storage_path=None,
+        workflow_priority=None,
+    ):  # noqa: E501
+        """TaskSummary - a model defined in Swagger"""  # noqa: E501
+        self._workflow_id = None
+        self._workflow_type = None
+        self._correlation_id = None
+        self._scheduled_time = None
+        self._start_time = None
+        self._update_time = None
+        self._end_time = None
+        self._status = None
+        self._reason_for_incompletion = None
+        self._execution_time = None
+        self._queue_wait_time = None
+        self._task_def_name = None
+        self._task_type = None
+        self._input = None
+        self._output = None
+        self._task_id = None
+        self._external_input_payload_storage_path = None
+        self._external_output_payload_storage_path = None
+        self._workflow_priority = None
+        self.discriminator = None
+        if workflow_id is not None:
+            self.workflow_id = workflow_id
+        if workflow_type is not None:
+            self.workflow_type = workflow_type
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if scheduled_time is not None:
+            self.scheduled_time = scheduled_time
+        if start_time is not None:
+            self.start_time = start_time
+        if update_time is not None:
+            self.update_time = update_time
+        if end_time is not None:
+            self.end_time = end_time
+        if status is not None:
+            self.status = status
+        if reason_for_incompletion is not None:
+            self.reason_for_incompletion = reason_for_incompletion
+        if execution_time is not None:
+            self.execution_time = execution_time
+        if queue_wait_time is not None:
+            self.queue_wait_time = queue_wait_time
+        if task_def_name is not None:
+            self.task_def_name = task_def_name
+        if task_type is not None:
+            self.task_type = task_type
+        if input is not None:
+            self.input = input
+        if output is not None:
+            self.output = output
+        if task_id is not None:
+            self.task_id = task_id
+        if external_input_payload_storage_path is not None:
+            self.external_input_payload_storage_path = (
+                external_input_payload_storage_path
+            )
+        if external_output_payload_storage_path is not None:
+            self.external_output_payload_storage_path = (
+                external_output_payload_storage_path
+            )
+        if workflow_priority is not None:
+            self.workflow_priority = workflow_priority
+
+    @property
+    def workflow_id(self):
+        """Gets the workflow_id of this TaskSummary.  # noqa: E501
+
+
+        :return: The workflow_id of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_id
+
+    @workflow_id.setter
+    def workflow_id(self, workflow_id):
+        """Sets the workflow_id of this TaskSummary.
+
+
+        :param workflow_id: The workflow_id of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_id = workflow_id
+
+    @property
+    def workflow_type(self):
+        """Gets the workflow_type of this TaskSummary.  # noqa: E501
+
+
+        :return: The workflow_type of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_type
+
+    @workflow_type.setter
+    def workflow_type(self, workflow_type):
+        """Sets the workflow_type of this TaskSummary.
+
+
+        :param workflow_type: The workflow_type of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_type = workflow_type
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this TaskSummary.  # noqa: E501
+
+
+        :return: The correlation_id of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this TaskSummary.
+
+
+        :param correlation_id: The correlation_id of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def scheduled_time(self):
+        """Gets the scheduled_time of this TaskSummary.  # noqa: E501
+
+
+        :return: The scheduled_time of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._scheduled_time
+
+    @scheduled_time.setter
+    def scheduled_time(self, scheduled_time):
+        """Sets the scheduled_time of this TaskSummary.
+
+
+        :param scheduled_time: The scheduled_time of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._scheduled_time = scheduled_time
+
+    @property
+    def start_time(self):
+        """Gets the start_time of this TaskSummary.  # noqa: E501
+
+
+        :return: The start_time of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._start_time
+
+    @start_time.setter
+    def start_time(self, start_time):
+        """Sets the start_time of this TaskSummary.
+
+
+        :param start_time: The start_time of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._start_time = start_time
+
+    @property
+    def update_time(self):
+        """Gets the update_time of this TaskSummary.  # noqa: E501
+
+
+        :return: The update_time of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._update_time
+
+    @update_time.setter
+    def update_time(self, update_time):
+        """Sets the update_time of this TaskSummary.
+
+
+        :param update_time: The update_time of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._update_time = update_time
+
+    @property
+    def end_time(self):
+        """Gets the end_time of this TaskSummary.  # noqa: E501
+
+
+        :return: The end_time of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._end_time
+
+    @end_time.setter
+    def end_time(self, end_time):
+        """Sets the end_time of this TaskSummary.
+
+
+        :param end_time: The end_time of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._end_time = end_time
+
+    @property
+    def status(self):
+        """Gets the status of this TaskSummary.  # noqa: E501
+
+
+        :return: The status of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    @status.setter
+    def status(self, status):
+        """Sets the status of this TaskSummary.
+
+
+        :param status: The status of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            "IN_PROGRESS",
+            "CANCELED",
+            "FAILED",
+            "FAILED_WITH_TERMINAL_ERROR",
+            "COMPLETED",
+            "COMPLETED_WITH_ERRORS",
+            "SCHEDULED",
+            "TIMED_OUT",
+            "SKIPPED",
+        ]  # noqa: E501
+        if status not in allowed_values:
+            raise ValueError(
+                "Invalid value for `status` ({0}), must be one of {1}".format(  # noqa: E501
+                    status, allowed_values
+                )
+            )
+
+        self._status = status
+
+    @property
+    def reason_for_incompletion(self):
+        """Gets the reason_for_incompletion of this TaskSummary.  # noqa: E501
+
+
+        :return: The reason_for_incompletion of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._reason_for_incompletion
+
+    @reason_for_incompletion.setter
+    def reason_for_incompletion(self, reason_for_incompletion):
+        """Sets the reason_for_incompletion of this TaskSummary.
+
+
+        :param reason_for_incompletion: The reason_for_incompletion of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._reason_for_incompletion = reason_for_incompletion
+
+    @property
+    def execution_time(self):
+        """Gets the execution_time of this TaskSummary.  # noqa: E501
+
+
+        :return: The execution_time of this TaskSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._execution_time
+
+    @execution_time.setter
+    def execution_time(self, execution_time):
+        """Sets the execution_time of this TaskSummary.
+
+
+        :param execution_time: The execution_time of this TaskSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._execution_time = execution_time
+
+    @property
+    def queue_wait_time(self):
+        """Gets the queue_wait_time of this TaskSummary.  # noqa: E501
+
+
+        :return: The queue_wait_time of this TaskSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._queue_wait_time
+
+    @queue_wait_time.setter
+    def queue_wait_time(self, queue_wait_time):
+        """Sets the queue_wait_time of this TaskSummary.
+
+
+        :param queue_wait_time: The queue_wait_time of this TaskSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._queue_wait_time = queue_wait_time
+
+    @property
+    def task_def_name(self):
+        """Gets the task_def_name of this TaskSummary.  # noqa: E501
+
+
+        :return: The task_def_name of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_def_name
+
+    @task_def_name.setter
+    def task_def_name(self, task_def_name):
+        """Sets the task_def_name of this TaskSummary.
+
+
+        :param task_def_name: The task_def_name of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._task_def_name = task_def_name
+
+    @property
+    def task_type(self):
+        """Gets the task_type of this TaskSummary.  # noqa: E501
+
+
+        :return: The task_type of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_type
+
+    @task_type.setter
+    def task_type(self, task_type):
+        """Sets the task_type of this TaskSummary.
+
+
+        :param task_type: The task_type of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._task_type = task_type
+
+    @property
+    def input(self):
+        """Gets the input of this TaskSummary.  # noqa: E501
+
+
+        :return: The input of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._input
+
+    @input.setter
+    def input(self, input):
+        """Sets the input of this TaskSummary.
+
+
+        :param input: The input of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._input = input
+
+    @property
+    def output(self):
+        """Gets the output of this TaskSummary.  # noqa: E501
+
+
+        :return: The output of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._output
+
+    @output.setter
+    def output(self, output):
+        """Sets the output of this TaskSummary.
+
+
+        :param output: The output of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._output = output
+
+    @property
+    def task_id(self):
+        """Gets the task_id of this TaskSummary.  # noqa: E501
+
+
+        :return: The task_id of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_id
+
+    @task_id.setter
+    def task_id(self, task_id):
+        """Sets the task_id of this TaskSummary.
+
+
+        :param task_id: The task_id of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._task_id = task_id
+
+    @property
+    def external_input_payload_storage_path(self):
+        """Gets the external_input_payload_storage_path of this TaskSummary.  # noqa: E501
+
+
+        :return: The external_input_payload_storage_path of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_input_payload_storage_path
+
+    @external_input_payload_storage_path.setter
+    def external_input_payload_storage_path(self, external_input_payload_storage_path):
+        """Sets the external_input_payload_storage_path of this TaskSummary.
+
+
+        :param external_input_payload_storage_path: The external_input_payload_storage_path of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._external_input_payload_storage_path = external_input_payload_storage_path
+
+    @property
+    def external_output_payload_storage_path(self):
+        """Gets the external_output_payload_storage_path of this TaskSummary.  # noqa: E501
+
+
+        :return: The external_output_payload_storage_path of this TaskSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_output_payload_storage_path
+
+    @external_output_payload_storage_path.setter
+    def external_output_payload_storage_path(
+        self, external_output_payload_storage_path
+    ):
+        """Sets the external_output_payload_storage_path of this TaskSummary.
+
+
+        :param external_output_payload_storage_path: The external_output_payload_storage_path of this TaskSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._external_output_payload_storage_path = (
+            external_output_payload_storage_path
+        )
+
+    @property
+    def workflow_priority(self):
+        """Gets the workflow_priority of this TaskSummary.  # noqa: E501
+
+
+        :return: The workflow_priority of this TaskSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._workflow_priority
+
+    @workflow_priority.setter
+    def workflow_priority(self, workflow_priority):
+        """Sets the workflow_priority of this TaskSummary.
+
+
+        :param workflow_priority: The workflow_priority of this TaskSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._workflow_priority = workflow_priority
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(TaskSummary, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, TaskSummary):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/token.py b/omagent_core/engine/http/models/token.py
new file mode 100644
index 0000000000000000000000000000000000000000..b80d8d2cd894d6f53136fde8f06c59399684b53f
--- /dev/null
+++ b/omagent_core/engine/http/models/token.py
@@ -0,0 +1,17 @@
+class Token(object):
+    swagger_types = {"token": "str"}
+
+    attribute_map = {"token": "token"}
+
+    def __init__(self, token: str = None):
+        self.token = None
+        if token is not None:
+            self.token = token
+
+    @property
+    def token(self) -> str:
+        return self._token
+
+    @token.setter
+    def token(self, token: str):
+        self._token = token
diff --git a/omagent_core/engine/http/models/upsert_group_request.py b/omagent_core/engine/http/models/upsert_group_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..d2894236f4ca4a92ba059572b03dde2125a4bdb7
--- /dev/null
+++ b/omagent_core/engine/http/models/upsert_group_request.py
@@ -0,0 +1,138 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class UpsertGroupRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"description": "str", "roles": "list[str]"}
+
+    attribute_map = {"description": "description", "roles": "roles"}
+
+    def __init__(self, description=None, roles=None):  # noqa: E501
+        """UpsertGroupRequest - a model defined in Swagger"""  # noqa: E501
+        self._description = None
+        self._roles = None
+        self.discriminator = None
+        self.description = description
+        if roles is not None:
+            self.roles = roles
+
+    @property
+    def description(self):
+        """Gets the description of this UpsertGroupRequest.  # noqa: E501
+
+        A general description of the group  # noqa: E501
+
+        :return: The description of this UpsertGroupRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this UpsertGroupRequest.
+
+        A general description of the group  # noqa: E501
+
+        :param description: The description of this UpsertGroupRequest.  # noqa: E501
+        :type: str
+        """
+        self._description = description
+
+    @property
+    def roles(self):
+        """Gets the roles of this UpsertGroupRequest.  # noqa: E501
+
+
+        :return: The roles of this UpsertGroupRequest.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._roles
+
+    @roles.setter
+    def roles(self, roles):
+        """Sets the roles of this UpsertGroupRequest.
+
+
+        :param roles: The roles of this UpsertGroupRequest.  # noqa: E501
+        :type: list[str]
+        """
+        allowed_values = [
+            "ADMIN",
+            "USER",
+            "WORKER",
+            "METADATA_MANAGER",
+            "WORKFLOW_MANAGER",
+        ]  # noqa: E501
+        if not set(roles).issubset(set(allowed_values)):
+            raise ValueError(
+                "Invalid values for `roles` [{0}], must be a subset of [{1}]".format(  # noqa: E501
+                    ", ".join(map(str, set(roles) - set(allowed_values))),  # noqa: E501
+                    ", ".join(map(str, allowed_values)),
+                )
+            )
+
+        self._roles = roles
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(UpsertGroupRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, UpsertGroupRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/upsert_user_request.py b/omagent_core/engine/http/models/upsert_user_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..81a5cf5ea890fb0d56f98476901278c8ecd64664
--- /dev/null
+++ b/omagent_core/engine/http/models/upsert_user_request.py
@@ -0,0 +1,164 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class UpsertUserRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"name": "str", "roles": "list[str]", "groups": "list[str]"}
+
+    attribute_map = {"name": "name", "roles": "roles", "groups": "groups"}
+
+    def __init__(self, name=None, roles=None, groups=None):  # noqa: E501
+        """UpsertUserRequest - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._roles = None
+        self._groups = None
+        self.discriminator = None
+        self.name = name
+        if roles is not None:
+            self.roles = roles
+        if groups is not None:
+            self.groups = groups
+
+    @property
+    def name(self):
+        """Gets the name of this UpsertUserRequest.  # noqa: E501
+
+        User's full name  # noqa: E501
+
+        :return: The name of this UpsertUserRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this UpsertUserRequest.
+
+        User's full name  # noqa: E501
+
+        :param name: The name of this UpsertUserRequest.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def roles(self):
+        """Gets the roles of this UpsertUserRequest.  # noqa: E501
+
+
+        :return: The roles of this UpsertUserRequest.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._roles
+
+    @roles.setter
+    def roles(self, roles):
+        """Sets the roles of this UpsertUserRequest.
+
+
+        :param roles: The roles of this UpsertUserRequest.  # noqa: E501
+        :type: list[str]
+        """
+        allowed_values = [
+            "ADMIN",
+            "USER",
+            "WORKER",
+            "METADATA_MANAGER",
+            "WORKFLOW_MANAGER",
+        ]  # noqa: E501
+        if not set(roles).issubset(set(allowed_values)):
+            raise ValueError(
+                "Invalid values for `roles` [{0}], must be a subset of [{1}]".format(  # noqa: E501
+                    ", ".join(map(str, set(roles) - set(allowed_values))),  # noqa: E501
+                    ", ".join(map(str, allowed_values)),
+                )
+            )
+
+        self._roles = roles
+
+    @property
+    def groups(self):
+        """Gets the groups of this UpsertUserRequest.  # noqa: E501
+
+        Ids of the groups this user belongs to  # noqa: E501
+
+        :return: The groups of this UpsertUserRequest.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._groups
+
+    @groups.setter
+    def groups(self, groups):
+        """Sets the groups of this UpsertUserRequest.
+
+        Ids of the groups this user belongs to  # noqa: E501
+
+        :param groups: The groups of this UpsertUserRequest.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._groups = groups
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(UpsertUserRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, UpsertUserRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow.py b/omagent_core/engine/http/models/workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..8bd34231da2618a18973cfa5932798e8da29aab2
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow.py
@@ -0,0 +1,903 @@
+import pprint
+import re  # noqa: F401
+
+import six
+from omagent_core.engine.http.models import Task
+from omagent_core.engine.http.models.workflow_run import (running_status,
+                                                          successful_status,
+                                                          terminal_status)
+
+
+class Workflow(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "owner_app": "str",
+        "create_time": "int",
+        "update_time": "int",
+        "created_by": "str",
+        "updated_by": "str",
+        "status": "str",
+        "end_time": "int",
+        "workflow_id": "str",
+        "parent_workflow_id": "str",
+        "parent_workflow_task_id": "str",
+        "tasks": "list[Task]",
+        "input": "dict(str, object)",
+        "output": "dict(str, object)",
+        "correlation_id": "str",
+        "re_run_from_workflow_id": "str",
+        "reason_for_incompletion": "str",
+        "event": "str",
+        "task_to_domain": "dict(str, str)",
+        "failed_reference_task_names": "list[str]",
+        "workflow_definition": "WorkflowDef",
+        "external_input_payload_storage_path": "str",
+        "external_output_payload_storage_path": "str",
+        "priority": "int",
+        "variables": "dict(str, object)",
+        "last_retried_time": "int",
+        "start_time": "int",
+        "workflow_name": "str",
+        "workflow_version": "int",
+    }
+
+    attribute_map = {
+        "owner_app": "ownerApp",
+        "create_time": "createTime",
+        "update_time": "updateTime",
+        "created_by": "createdBy",
+        "updated_by": "updatedBy",
+        "status": "status",
+        "end_time": "endTime",
+        "workflow_id": "workflowId",
+        "parent_workflow_id": "parentWorkflowId",
+        "parent_workflow_task_id": "parentWorkflowTaskId",
+        "tasks": "tasks",
+        "input": "input",
+        "output": "output",
+        "correlation_id": "correlationId",
+        "re_run_from_workflow_id": "reRunFromWorkflowId",
+        "reason_for_incompletion": "reasonForIncompletion",
+        "event": "event",
+        "task_to_domain": "taskToDomain",
+        "failed_reference_task_names": "failedReferenceTaskNames",
+        "workflow_definition": "workflowDefinition",
+        "external_input_payload_storage_path": "externalInputPayloadStoragePath",
+        "external_output_payload_storage_path": "externalOutputPayloadStoragePath",
+        "priority": "priority",
+        "variables": "variables",
+        "last_retried_time": "lastRetriedTime",
+        "start_time": "startTime",
+        "workflow_name": "workflowName",
+        "workflow_version": "workflowVersion",
+    }
+
+    def __init__(
+        self,
+        owner_app=None,
+        create_time=None,
+        update_time=None,
+        created_by=None,
+        updated_by=None,
+        status=None,
+        end_time=None,
+        workflow_id=None,
+        parent_workflow_id=None,
+        parent_workflow_task_id=None,
+        tasks=None,
+        input=None,
+        output=None,
+        correlation_id=None,
+        re_run_from_workflow_id=None,
+        reason_for_incompletion=None,
+        event=None,
+        task_to_domain=None,
+        failed_reference_task_names=None,
+        workflow_definition=None,
+        external_input_payload_storage_path=None,
+        external_output_payload_storage_path=None,
+        priority=None,
+        variables=None,
+        last_retried_time=None,
+        start_time=None,
+        workflow_name=None,
+        workflow_version=None,
+    ):  # noqa: E501
+        """Workflow - a model defined in Swagger"""  # noqa: E501
+        self._owner_app = None
+        self._create_time = None
+        self._update_time = None
+        self._created_by = None
+        self._updated_by = None
+        self._status = None
+        self._end_time = None
+        self._workflow_id = None
+        self._parent_workflow_id = None
+        self._parent_workflow_task_id = None
+        self._tasks = None
+        self._input = None
+        self._output = None
+        self._correlation_id = None
+        self._re_run_from_workflow_id = None
+        self._reason_for_incompletion = None
+        self._event = None
+        self._task_to_domain = None
+        self._failed_reference_task_names = None
+        self._workflow_definition = None
+        self._external_input_payload_storage_path = None
+        self._external_output_payload_storage_path = None
+        self._priority = None
+        self._variables = None
+        self._last_retried_time = None
+        self._start_time = None
+        self._workflow_name = None
+        self._workflow_version = None
+        self.discriminator = None
+        if owner_app is not None:
+            self.owner_app = owner_app
+        if create_time is not None:
+            self.create_time = create_time
+        if update_time is not None:
+            self.update_time = update_time
+        if created_by is not None:
+            self.created_by = created_by
+        if updated_by is not None:
+            self.updated_by = updated_by
+        if status is not None:
+            self.status = status
+        if end_time is not None:
+            self.end_time = end_time
+        if workflow_id is not None:
+            self.workflow_id = workflow_id
+        if parent_workflow_id is not None:
+            self.parent_workflow_id = parent_workflow_id
+        if parent_workflow_task_id is not None:
+            self.parent_workflow_task_id = parent_workflow_task_id
+        if tasks is not None:
+            self.tasks = tasks
+        if input is not None:
+            self.input = input
+        if output is not None:
+            self.output = output
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if re_run_from_workflow_id is not None:
+            self.re_run_from_workflow_id = re_run_from_workflow_id
+        if reason_for_incompletion is not None:
+            self.reason_for_incompletion = reason_for_incompletion
+        if event is not None:
+            self.event = event
+        if task_to_domain is not None:
+            self.task_to_domain = task_to_domain
+        if failed_reference_task_names is not None:
+            self.failed_reference_task_names = failed_reference_task_names
+        if workflow_definition is not None:
+            self.workflow_definition = workflow_definition
+        if external_input_payload_storage_path is not None:
+            self.external_input_payload_storage_path = (
+                external_input_payload_storage_path
+            )
+        if external_output_payload_storage_path is not None:
+            self.external_output_payload_storage_path = (
+                external_output_payload_storage_path
+            )
+        if priority is not None:
+            self.priority = priority
+        if variables is not None:
+            self.variables = variables
+        if last_retried_time is not None:
+            self.last_retried_time = last_retried_time
+        if start_time is not None:
+            self.start_time = start_time
+        if workflow_name is not None:
+            self.workflow_name = workflow_name
+        if workflow_version is not None:
+            self.workflow_version = workflow_version
+
+    @property
+    def owner_app(self):
+        """Gets the owner_app of this Workflow.  # noqa: E501
+
+
+        :return: The owner_app of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._owner_app
+
+    @owner_app.setter
+    def owner_app(self, owner_app):
+        """Sets the owner_app of this Workflow.
+
+
+        :param owner_app: The owner_app of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._owner_app = owner_app
+
+    @property
+    def create_time(self):
+        """Gets the create_time of this Workflow.  # noqa: E501
+
+
+        :return: The create_time of this Workflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._create_time
+
+    @create_time.setter
+    def create_time(self, create_time):
+        """Sets the create_time of this Workflow.
+
+
+        :param create_time: The create_time of this Workflow.  # noqa: E501
+        :type: int
+        """
+
+        self._create_time = create_time
+
+    @property
+    def update_time(self):
+        """Gets the update_time of this Workflow.  # noqa: E501
+
+
+        :return: The update_time of this Workflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._update_time
+
+    @update_time.setter
+    def update_time(self, update_time):
+        """Sets the update_time of this Workflow.
+
+
+        :param update_time: The update_time of this Workflow.  # noqa: E501
+        :type: int
+        """
+
+        self._update_time = update_time
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this Workflow.  # noqa: E501
+
+
+        :return: The created_by of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this Workflow.
+
+
+        :param created_by: The created_by of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this Workflow.  # noqa: E501
+
+
+        :return: The updated_by of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this Workflow.
+
+
+        :param updated_by: The updated_by of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    @property
+    def status(self) -> str:
+        """Gets the status of this Workflow.  # noqa: E501
+
+
+        :return: The status of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    def is_completed(self) -> bool:
+        """Checks if the workflow has completed
+        :return: True if the workflow status is COMPLETED, FAILED or TERMINATED
+        """
+        return self.status in terminal_status
+
+    def is_successful(self) -> bool:
+        """Checks if the workflow has completed in successful state (ie COMPLETED)
+        :return: True if the workflow status is COMPLETED
+        """
+        return self._status in successful_status
+
+    def is_running(self) -> bool:
+        return self.status in running_status
+
+    def status(self, status):
+        """Sets the status of this Workflow.
+
+
+        :param status: The status of this Workflow.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            "RUNNING",
+            "COMPLETED",
+            "FAILED",
+            "TIMED_OUT",
+            "TERMINATED",
+            "PAUSED",
+        ]  # noqa: E501
+        if status not in allowed_values:
+            raise ValueError(
+                "Invalid value for `status` ({0}), must be one of {1}".format(  # noqa: E501
+                    status, allowed_values
+                )
+            )
+
+        self._status = status
+
+    @property
+    def end_time(self):
+        """Gets the end_time of this Workflow.  # noqa: E501
+
+
+        :return: The end_time of this Workflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._end_time
+
+    @end_time.setter
+    def end_time(self, end_time):
+        """Sets the end_time of this Workflow.
+
+
+        :param end_time: The end_time of this Workflow.  # noqa: E501
+        :type: int
+        """
+
+        self._end_time = end_time
+
+    @property
+    def workflow_id(self):
+        """Gets the workflow_id of this Workflow.  # noqa: E501
+
+
+        :return: The workflow_id of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_id
+
+    @workflow_id.setter
+    def workflow_id(self, workflow_id):
+        """Sets the workflow_id of this Workflow.
+
+
+        :param workflow_id: The workflow_id of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_id = workflow_id
+
+    @property
+    def parent_workflow_id(self):
+        """Gets the parent_workflow_id of this Workflow.  # noqa: E501
+
+
+        :return: The parent_workflow_id of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._parent_workflow_id
+
+    @parent_workflow_id.setter
+    def parent_workflow_id(self, parent_workflow_id):
+        """Sets the parent_workflow_id of this Workflow.
+
+
+        :param parent_workflow_id: The parent_workflow_id of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._parent_workflow_id = parent_workflow_id
+
+    @property
+    def parent_workflow_task_id(self):
+        """Gets the parent_workflow_task_id of this Workflow.  # noqa: E501
+
+
+        :return: The parent_workflow_task_id of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._parent_workflow_task_id
+
+    @parent_workflow_task_id.setter
+    def parent_workflow_task_id(self, parent_workflow_task_id):
+        """Sets the parent_workflow_task_id of this Workflow.
+
+
+        :param parent_workflow_task_id: The parent_workflow_task_id of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._parent_workflow_task_id = parent_workflow_task_id
+
+    @property
+    def tasks(self):
+        """Gets the tasks of this Workflow.  # noqa: E501
+
+
+        :return: The tasks of this Workflow.  # noqa: E501
+        :rtype: list[Task]
+        """
+        return self._tasks
+
+    @tasks.setter
+    def tasks(self, tasks):
+        """Sets the tasks of this Workflow.
+
+
+        :param tasks: The tasks of this Workflow.  # noqa: E501
+        :type: list[Task]
+        """
+
+        self._tasks = tasks
+
+    @property
+    def input(self):
+        """Gets the input of this Workflow.  # noqa: E501
+
+
+        :return: The input of this Workflow.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input
+
+    @input.setter
+    def input(self, input):
+        """Sets the input of this Workflow.
+
+
+        :param input: The input of this Workflow.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input = input
+
+    @property
+    def output(self):
+        """Gets the output of this Workflow.  # noqa: E501
+
+
+        :return: The output of this Workflow.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._output
+
+    @output.setter
+    def output(self, output):
+        """Sets the output of this Workflow.
+
+
+        :param output: The output of this Workflow.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._output = output
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this Workflow.  # noqa: E501
+
+
+        :return: The correlation_id of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this Workflow.
+
+
+        :param correlation_id: The correlation_id of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def re_run_from_workflow_id(self):
+        """Gets the re_run_from_workflow_id of this Workflow.  # noqa: E501
+
+
+        :return: The re_run_from_workflow_id of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._re_run_from_workflow_id
+
+    @re_run_from_workflow_id.setter
+    def re_run_from_workflow_id(self, re_run_from_workflow_id):
+        """Sets the re_run_from_workflow_id of this Workflow.
+
+
+        :param re_run_from_workflow_id: The re_run_from_workflow_id of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._re_run_from_workflow_id = re_run_from_workflow_id
+
+    @property
+    def reason_for_incompletion(self):
+        """Gets the reason_for_incompletion of this Workflow.  # noqa: E501
+
+
+        :return: The reason_for_incompletion of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._reason_for_incompletion
+
+    @reason_for_incompletion.setter
+    def reason_for_incompletion(self, reason_for_incompletion):
+        """Sets the reason_for_incompletion of this Workflow.
+
+
+        :param reason_for_incompletion: The reason_for_incompletion of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._reason_for_incompletion = reason_for_incompletion
+
+    @property
+    def event(self):
+        """Gets the event of this Workflow.  # noqa: E501
+
+
+        :return: The event of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._event
+
+    @event.setter
+    def event(self, event):
+        """Sets the event of this Workflow.
+
+
+        :param event: The event of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._event = event
+
+    @property
+    def task_to_domain(self):
+        """Gets the task_to_domain of this Workflow.  # noqa: E501
+
+
+        :return: The task_to_domain of this Workflow.  # noqa: E501
+        :rtype: dict(str, str)
+        """
+        return self._task_to_domain
+
+    @task_to_domain.setter
+    def task_to_domain(self, task_to_domain):
+        """Sets the task_to_domain of this Workflow.
+
+
+        :param task_to_domain: The task_to_domain of this Workflow.  # noqa: E501
+        :type: dict(str, str)
+        """
+
+        self._task_to_domain = task_to_domain
+
+    @property
+    def failed_reference_task_names(self):
+        """Gets the failed_reference_task_names of this Workflow.  # noqa: E501
+
+
+        :return: The failed_reference_task_names of this Workflow.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._failed_reference_task_names
+
+    @failed_reference_task_names.setter
+    def failed_reference_task_names(self, failed_reference_task_names):
+        """Sets the failed_reference_task_names of this Workflow.
+
+
+        :param failed_reference_task_names: The failed_reference_task_names of this Workflow.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._failed_reference_task_names = failed_reference_task_names
+
+    @property
+    def workflow_definition(self):
+        """Gets the workflow_definition of this Workflow.  # noqa: E501
+
+
+        :return: The workflow_definition of this Workflow.  # noqa: E501
+        :rtype: WorkflowDef
+        """
+        return self._workflow_definition
+
+    @workflow_definition.setter
+    def workflow_definition(self, workflow_definition):
+        """Sets the workflow_definition of this Workflow.
+
+
+        :param workflow_definition: The workflow_definition of this Workflow.  # noqa: E501
+        :type: WorkflowDef
+        """
+
+        self._workflow_definition = workflow_definition
+
+    @property
+    def external_input_payload_storage_path(self):
+        """Gets the external_input_payload_storage_path of this Workflow.  # noqa: E501
+
+
+        :return: The external_input_payload_storage_path of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_input_payload_storage_path
+
+    @external_input_payload_storage_path.setter
+    def external_input_payload_storage_path(self, external_input_payload_storage_path):
+        """Sets the external_input_payload_storage_path of this Workflow.
+
+
+        :param external_input_payload_storage_path: The external_input_payload_storage_path of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._external_input_payload_storage_path = external_input_payload_storage_path
+
+    @property
+    def external_output_payload_storage_path(self):
+        """Gets the external_output_payload_storage_path of this Workflow.  # noqa: E501
+
+
+        :return: The external_output_payload_storage_path of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_output_payload_storage_path
+
+    @external_output_payload_storage_path.setter
+    def external_output_payload_storage_path(
+        self, external_output_payload_storage_path
+    ):
+        """Sets the external_output_payload_storage_path of this Workflow.
+
+
+        :param external_output_payload_storage_path: The external_output_payload_storage_path of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._external_output_payload_storage_path = (
+            external_output_payload_storage_path
+        )
+
+    @property
+    def priority(self):
+        """Gets the priority of this Workflow.  # noqa: E501
+
+
+        :return: The priority of this Workflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._priority
+
+    @priority.setter
+    def priority(self, priority):
+        """Sets the priority of this Workflow.
+
+
+        :param priority: The priority of this Workflow.  # noqa: E501
+        :type: int
+        """
+
+        self._priority = priority
+
+    @property
+    def variables(self):
+        """Gets the variables of this Workflow.  # noqa: E501
+
+
+        :return: The variables of this Workflow.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._variables
+
+    @variables.setter
+    def variables(self, variables):
+        """Sets the variables of this Workflow.
+
+
+        :param variables: The variables of this Workflow.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._variables = variables
+
+    @property
+    def last_retried_time(self):
+        """Gets the last_retried_time of this Workflow.  # noqa: E501
+
+
+        :return: The last_retried_time of this Workflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._last_retried_time
+
+    @last_retried_time.setter
+    def last_retried_time(self, last_retried_time):
+        """Sets the last_retried_time of this Workflow.
+
+
+        :param last_retried_time: The last_retried_time of this Workflow.  # noqa: E501
+        :type: int
+        """
+
+        self._last_retried_time = last_retried_time
+
+    @property
+    def start_time(self):
+        """Gets the start_time of this Workflow.  # noqa: E501
+
+
+        :return: The start_time of this Workflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._start_time
+
+    @start_time.setter
+    def start_time(self, start_time):
+        """Sets the start_time of this Workflow.
+
+
+        :param start_time: The start_time of this Workflow.  # noqa: E501
+        :type: int
+        """
+
+        self._start_time = start_time
+
+    @property
+    def workflow_name(self):
+        """Gets the workflow_name of this Workflow.  # noqa: E501
+
+
+        :return: The workflow_name of this Workflow.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_name
+
+    @workflow_name.setter
+    def workflow_name(self, workflow_name):
+        """Sets the workflow_name of this Workflow.
+
+
+        :param workflow_name: The workflow_name of this Workflow.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_name = workflow_name
+
+    @property
+    def workflow_version(self):
+        """Gets the workflow_version of this Workflow.  # noqa: E501
+
+
+        :return: The workflow_version of this Workflow.  # noqa: E501
+        :rtype: int
+        """
+        return self._workflow_version
+
+    @workflow_version.setter
+    def workflow_version(self, workflow_version):
+        """Sets the workflow_version of this Workflow.
+
+
+        :param workflow_version: The workflow_version of this Workflow.  # noqa: E501
+        :type: int
+        """
+
+        self._workflow_version = workflow_version
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(Workflow, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, Workflow):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
+
+    @property
+    def current_task(self) -> Task:
+        current = None
+        for task in self.tasks:
+            if task.status == "SCHEDULED" or task.status == "IN_PROGRESS":
+                current = task
+        return current
+
+    def get_task(self, name: str = None, task_reference_name: str = None) -> Task:
+        if name is None and task_reference_name is None:
+            raise Exception(
+                "ONLY one of name or task_reference_name MUST be provided.  None were provided"
+            )
+        if name is not None and not task_reference_name is None:
+            raise Exception(
+                "ONLY one of name or task_reference_name MUST be provided.  both were provided"
+            )
+
+        current = None
+        for task in self.tasks:
+            if (
+                task.task_def_name == name
+                or task.workflow_task.task_reference_name == task_reference_name
+            ):
+                current = task
+        return current
diff --git a/omagent_core/engine/http/models/workflow_def.py b/omagent_core/engine/http/models/workflow_def.py
new file mode 100644
index 0000000000000000000000000000000000000000..30ba1bdf8b79821e2f1b7a8802024fa21632bd44
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_def.py
@@ -0,0 +1,650 @@
+import json
+import pprint
+import re  # noqa: F401
+from typing import List
+
+import six
+from omagent_core.engine.helpers.helper import ObjectMapper
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+
+object_mapper = ObjectMapper()
+
+
+class WorkflowDef(object):
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+
+    swagger_types = {
+        "owner_app": "str",
+        "create_time": "int",
+        "update_time": "int",
+        "created_by": "str",
+        "updated_by": "str",
+        "name": "str",
+        "description": "str",
+        "version": "int",
+        "tasks": "list[WorkflowTask]",
+        "input_parameters": "list[str]",
+        "output_parameters": "dict(str, object)",
+        "failure_workflow": "str",
+        "schema_version": "int",
+        "restartable": "bool",
+        "workflow_status_listener_enabled": "bool",
+        "workflow_status_listener_sink": "str",
+        "owner_email": "str",
+        "timeout_policy": "str",
+        "timeout_seconds": "int",
+        "variables": "dict(str, object)",
+        "input_template": "dict(str, object)",
+    }
+
+    attribute_map = {
+        "owner_app": "ownerApp",
+        "create_time": "createTime",
+        "update_time": "updateTime",
+        "created_by": "createdBy",
+        "updated_by": "updatedBy",
+        "name": "name",
+        "description": "description",
+        "version": "version",
+        "tasks": "tasks",
+        "input_parameters": "inputParameters",
+        "output_parameters": "outputParameters",
+        "failure_workflow": "failureWorkflow",
+        "schema_version": "schemaVersion",
+        "restartable": "restartable",
+        "workflow_status_listener_enabled": "workflowStatusListenerEnabled",
+        "workflow_status_listener_sink": "workflowStatusListenerSink",
+        "owner_email": "ownerEmail",
+        "timeout_policy": "timeoutPolicy",
+        "timeout_seconds": "timeoutSeconds",
+        "variables": "variables",
+        "input_template": "inputTemplate",
+    }
+
+    def __init__(
+        self,
+        owner_app=None,
+        create_time=None,
+        update_time=None,
+        created_by=None,
+        updated_by=None,
+        name=None,
+        description=None,
+        version=None,
+        tasks=None,
+        input_parameters=None,
+        output_parameters: dict = {},
+        failure_workflow=None,
+        schema_version=None,
+        restartable=None,
+        workflow_status_listener_enabled=None,
+        workflow_status_listener_sink=None,
+        owner_email=None,
+        timeout_policy=None,
+        timeout_seconds=None,
+        variables=None,
+        input_template=None,
+    ):  # noqa: E501
+        """WorkflowDef - a model defined in Swagger"""  # noqa: E501
+        self._owner_app = None
+        self._create_time = None
+        self._update_time = None
+        self._created_by = None
+        self._updated_by = None
+        self._name = None
+        self._description = None
+        self._version = None
+        self._tasks = []
+        self._input_parameters = None
+        self._output_parameters = None
+        self._failure_workflow = None
+        self._schema_version = None
+        self._restartable = None
+        self._workflow_status_listener_enabled = None
+        self._workflow_status_listener_sink = None
+        self._owner_email = None
+        self._timeout_policy = None
+        self._timeout_seconds = None
+        self._variables = None
+        self._input_template = None
+        self.discriminator = None
+        if owner_app is not None:
+            self.owner_app = owner_app
+        if create_time is not None:
+            self.create_time = create_time
+        if update_time is not None:
+            self.update_time = update_time
+        if created_by is not None:
+            self.created_by = created_by
+        if updated_by is not None:
+            self.updated_by = updated_by
+        self.name = name
+        if description is not None:
+            self.description = description
+        if version is not None:
+            self.version = version
+        self.tasks = tasks
+        if input_parameters is not None:
+            self.input_parameters = input_parameters
+        if output_parameters is not None:
+            self.output_parameters = output_parameters
+        if failure_workflow is not None:
+            self.failure_workflow = failure_workflow
+        if schema_version is not None:
+            self.schema_version = schema_version
+        if restartable is not None:
+            self.restartable = restartable
+        if workflow_status_listener_enabled is not None:
+            self._workflow_status_listener_enabled = workflow_status_listener_enabled
+        if workflow_status_listener_sink is not None:
+            self._workflow_status_listener_sink = workflow_status_listener_sink
+        if owner_email is not None:
+            self.owner_email = owner_email
+        if timeout_policy is not None:
+            self.timeout_policy = timeout_policy
+        self.timeout_seconds = timeout_seconds
+        if variables is not None:
+            self.variables = variables
+        if input_template is not None:
+            self.input_template = input_template
+
+    @property
+    def owner_app(self):
+        """Gets the owner_app of this WorkflowDef.  # noqa: E501
+
+
+        :return: The owner_app of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._owner_app
+
+    @owner_app.setter
+    def owner_app(self, owner_app):
+        """Sets the owner_app of this WorkflowDef.
+
+
+        :param owner_app: The owner_app of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+
+        self._owner_app = owner_app
+
+    @property
+    def create_time(self):
+        """Gets the create_time of this WorkflowDef.  # noqa: E501
+
+
+        :return: The create_time of this WorkflowDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._create_time
+
+    @create_time.setter
+    def create_time(self, create_time):
+        """Sets the create_time of this WorkflowDef.
+
+
+        :param create_time: The create_time of this WorkflowDef.  # noqa: E501
+        :type: int
+        """
+
+        self._create_time = create_time
+
+    @property
+    def update_time(self):
+        """Gets the update_time of this WorkflowDef.  # noqa: E501
+
+
+        :return: The update_time of this WorkflowDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._update_time
+
+    @update_time.setter
+    def update_time(self, update_time):
+        """Sets the update_time of this WorkflowDef.
+
+
+        :param update_time: The update_time of this WorkflowDef.  # noqa: E501
+        :type: int
+        """
+
+        self._update_time = update_time
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this WorkflowDef.  # noqa: E501
+
+
+        :return: The created_by of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this WorkflowDef.
+
+
+        :param created_by: The created_by of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this WorkflowDef.  # noqa: E501
+
+
+        :return: The updated_by of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this WorkflowDef.
+
+
+        :param updated_by: The updated_by of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    @property
+    def name(self):
+        """Gets the name of this WorkflowDef.  # noqa: E501
+
+
+        :return: The name of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this WorkflowDef.
+
+
+        :param name: The name of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def description(self):
+        """Gets the description of this WorkflowDef.  # noqa: E501
+
+
+        :return: The description of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this WorkflowDef.
+
+
+        :param description: The description of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def version(self):
+        """Gets the version of this WorkflowDef.  # noqa: E501
+
+
+        :return: The version of this WorkflowDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._version
+
+    @version.setter
+    def version(self, version):
+        """Sets the version of this WorkflowDef.
+
+
+        :param version: The version of this WorkflowDef.  # noqa: E501
+        :type: int
+        """
+
+        self._version = version
+
+    @property
+    def tasks(self):
+        """Gets the tasks of this WorkflowDef.  # noqa: E501
+
+
+        :return: The tasks of this WorkflowDef.  # noqa: E501
+        :rtype: list[WorkflowTask]
+        """
+        if self._tasks is None:
+            self._tasks = []
+        return self._tasks
+
+    @tasks.setter
+    def tasks(self, tasks: List[WorkflowTask]):
+        """Sets the tasks of this WorkflowDef.
+
+
+        :param tasks: The tasks of this WorkflowDef.  # noqa: E501
+        :type: list[WorkflowTask]
+        """
+        self._tasks = tasks
+
+    @property
+    def input_parameters(self):
+        """Gets the input_parameters of this WorkflowDef.  # noqa: E501
+
+
+        :return: The input_parameters of this WorkflowDef.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._input_parameters
+
+    @input_parameters.setter
+    def input_parameters(self, input_parameters):
+        """Sets the input_parameters of this WorkflowDef.
+
+
+        :param input_parameters: The input_parameters of this WorkflowDef.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._input_parameters = input_parameters
+
+    @property
+    def output_parameters(self):
+        """Gets the output_parameters of this WorkflowDef.  # noqa: E501
+
+
+        :return: The output_parameters of this WorkflowDef.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._output_parameters
+
+    @output_parameters.setter
+    def output_parameters(self, output_parameters):
+        """Sets the output_parameters of this WorkflowDef.
+
+
+        :param output_parameters: The output_parameters of this WorkflowDef.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._output_parameters = output_parameters
+
+    @property
+    def failure_workflow(self):
+        """Gets the failure_workflow of this WorkflowDef.  # noqa: E501
+
+
+        :return: The failure_workflow of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._failure_workflow
+
+    @failure_workflow.setter
+    def failure_workflow(self, failure_workflow):
+        """Sets the failure_workflow of this WorkflowDef.
+
+
+        :param failure_workflow: The failure_workflow of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+
+        self._failure_workflow = failure_workflow
+
+    @property
+    def schema_version(self):
+        """Gets the schema_version of this WorkflowDef.  # noqa: E501
+
+
+        :return: The schema_version of this WorkflowDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._schema_version
+
+    @schema_version.setter
+    def schema_version(self, schema_version):
+        """Sets the schema_version of this WorkflowDef.
+
+
+        :param schema_version: The schema_version of this WorkflowDef.  # noqa: E501
+        :type: int
+        """
+
+        self._schema_version = schema_version
+
+    @property
+    def restartable(self):
+        """Gets the restartable of this WorkflowDef.  # noqa: E501
+
+
+        :return: The restartable of this WorkflowDef.  # noqa: E501
+        :rtype: bool
+        """
+        return self._restartable
+
+    @restartable.setter
+    def restartable(self, restartable):
+        """Sets the restartable of this WorkflowDef.
+
+
+        :param restartable: The restartable of this WorkflowDef.  # noqa: E501
+        :type: bool
+        """
+
+        self._restartable = restartable
+
+    @property
+    def workflow_status_listener_enabled(self):
+        """Gets the workflow_status_listener_enabled of this WorkflowDef.  # noqa: E501
+
+
+        :return: The workflow_status_listener_enabled of this WorkflowDef.  # noqa: E501
+        :rtype: bool
+        """
+        return self._workflow_status_listener_enabled
+
+    @workflow_status_listener_enabled.setter
+    def workflow_status_listener_enabled(self, workflow_status_listener_enabled):
+        """Sets the workflow_status_listener_enabled of this WorkflowDef.
+
+
+        :param workflow_status_listener_enabled: The workflow_status_listener_enabled of this WorkflowDef.  # noqa: E501
+        :type: bool
+        """
+
+        self._workflow_status_listener_enabled = workflow_status_listener_enabled
+
+    @property
+    def workflow_status_listener_sink(self):
+        return self._workflow_status_listener_sink
+
+    @workflow_status_listener_sink.setter
+    def workflow_status_listener_sink(self, workflow_status_listener_sink):
+        self._workflow_status_listener_sink = workflow_status_listener_sink
+
+    @property
+    def owner_email(self):
+        """Gets the owner_email of this WorkflowDef.  # noqa: E501
+
+
+        :return: The owner_email of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._owner_email
+
+    @owner_email.setter
+    def owner_email(self, owner_email):
+        """Sets the owner_email of this WorkflowDef.
+
+
+        :param owner_email: The owner_email of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+
+        self._owner_email = owner_email
+
+    @property
+    def timeout_policy(self):
+        """Gets the timeout_policy of this WorkflowDef.  # noqa: E501
+
+
+        :return: The timeout_policy of this WorkflowDef.  # noqa: E501
+        :rtype: str
+        """
+        return self._timeout_policy
+
+    @timeout_policy.setter
+    def timeout_policy(self, timeout_policy):
+        """Sets the timeout_policy of this WorkflowDef.
+
+
+        :param timeout_policy: The timeout_policy of this WorkflowDef.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["TIME_OUT_WF", "ALERT_ONLY"]  # noqa: E501
+        if timeout_policy not in allowed_values:
+            raise ValueError(
+                "Invalid value for `timeout_policy` ({0}), must be one of {1}".format(  # noqa: E501
+                    timeout_policy, allowed_values
+                )
+            )
+
+        self._timeout_policy = timeout_policy
+
+    @property
+    def timeout_seconds(self):
+        """Gets the timeout_seconds of this WorkflowDef.  # noqa: E501
+
+
+        :return: The timeout_seconds of this WorkflowDef.  # noqa: E501
+        :rtype: int
+        """
+        return self._timeout_seconds
+
+    @timeout_seconds.setter
+    def timeout_seconds(self, timeout_seconds):
+        """Sets the timeout_seconds of this WorkflowDef.
+
+
+        :param timeout_seconds: The timeout_seconds of this WorkflowDef.  # noqa: E501
+        :type: int
+        """
+        self._timeout_seconds = timeout_seconds
+
+    @property
+    def variables(self):
+        """Gets the variables of this WorkflowDef.  # noqa: E501
+
+
+        :return: The variables of this WorkflowDef.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._variables
+
+    @variables.setter
+    def variables(self, variables):
+        """Sets the variables of this WorkflowDef.
+
+
+        :param variables: The variables of this WorkflowDef.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._variables = variables
+
+    @property
+    def input_template(self):
+        """Gets the input_template of this WorkflowDef.  # noqa: E501
+
+
+        :return: The input_template of this WorkflowDef.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input_template
+
+    @input_template.setter
+    def input_template(self, input_template):
+        """Sets the input_template of this WorkflowDef.
+
+
+        :param input_template: The input_template of this WorkflowDef.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input_template = input_template
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowDef, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowDef):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
+
+    def toJSON(self):
+        return object_mapper.to_json(obj=self)
+
+
+def to_workflow_def(data: str = None, json_data: dict = None) -> WorkflowDef:
+    if json_data is not None:
+        return object_mapper.from_json(json_data, WorkflowDef)
+    if data is not None:
+        return object_mapper.from_json(json.loads(data), WorkflowDef)
+    raise Exception("missing data or json_data parameter")
diff --git a/omagent_core/engine/http/models/workflow_run.py b/omagent_core/engine/http/models/workflow_run.py
new file mode 100644
index 0000000000000000000000000000000000000000..e713470b2e4246cf3e63443c2f4365f8d5d5901c
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_run.py
@@ -0,0 +1,477 @@
+import pprint
+import re  # noqa: F401
+
+import six
+from omagent_core.engine.http.models import Task
+
+terminal_status = ("COMPLETED", "FAILED", "TIMED_OUT", "TERMINATED")
+successful_status = ("PAUSED", "COMPLETED")
+running_status = ("RUNNING", "PAUSED")
+
+
+class WorkflowRun(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "correlation_id": "str",
+        "create_time": "int",
+        "created_by": "str",
+        "input": "dict(str, object)",
+        "output": "dict(str, object)",
+        "priority": "int",
+        "request_id": "str",
+        "status": "str",
+        "tasks": "list[Task]",
+        "update_time": "int",
+        "variables": "dict(str, object)",
+        "workflow_id": "str",
+    }
+
+    attribute_map = {
+        "correlation_id": "correlationId",
+        "create_time": "createTime",
+        "created_by": "createdBy",
+        "input": "input",
+        "output": "output",
+        "priority": "priority",
+        "request_id": "requestId",
+        "status": "status",
+        "tasks": "tasks",
+        "update_time": "updateTime",
+        "variables": "variables",
+        "workflow_id": "workflowId",
+    }
+
+    def __init__(
+        self,
+        correlation_id=None,
+        create_time=None,
+        created_by=None,
+        input=None,
+        output=None,
+        priority=None,
+        request_id=None,
+        status=None,
+        tasks=None,
+        update_time=None,
+        variables=None,
+        workflow_id=None,
+        reason_for_incompletion: str = None,
+    ):  # noqa: E501
+        """WorkflowRun - a model defined in Swagger"""  # noqa: E501
+        self._correlation_id = None
+        self._create_time = None
+        self._created_by = None
+        self._input = None
+        self._output = None
+        self._priority = None
+        self._request_id = None
+        self._status = None
+        self._tasks = None
+        self._update_time = None
+        self._variables = None
+        self._workflow_id = None
+        self.discriminator = None
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if create_time is not None:
+            self.create_time = create_time
+        if created_by is not None:
+            self.created_by = created_by
+        if input is not None:
+            self.input = input
+        if output is not None:
+            self.output = output
+        if priority is not None:
+            self.priority = priority
+        if request_id is not None:
+            self.request_id = request_id
+        if status is not None:
+            self.status = status
+        if tasks is not None:
+            self.tasks = tasks
+        if update_time is not None:
+            self.update_time = update_time
+        if variables is not None:
+            self.variables = variables
+        if workflow_id is not None:
+            self.workflow_id = workflow_id
+        self._reason_for_incompletion = reason_for_incompletion
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this WorkflowRun.  # noqa: E501
+
+
+        :return: The correlation_id of this WorkflowRun.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this WorkflowRun.
+
+
+        :param correlation_id: The correlation_id of this WorkflowRun.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def create_time(self):
+        """Gets the create_time of this WorkflowRun.  # noqa: E501
+
+
+        :return: The create_time of this WorkflowRun.  # noqa: E501
+        :rtype: int
+        """
+        return self._create_time
+
+    @create_time.setter
+    def create_time(self, create_time):
+        """Sets the create_time of this WorkflowRun.
+
+
+        :param create_time: The create_time of this WorkflowRun.  # noqa: E501
+        :type: int
+        """
+
+        self._create_time = create_time
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this WorkflowRun.  # noqa: E501
+
+
+        :return: The created_by of this WorkflowRun.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this WorkflowRun.
+
+
+        :param created_by: The created_by of this WorkflowRun.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def input(self):
+        """Gets the input of this WorkflowRun.  # noqa: E501
+
+
+        :return: The input of this WorkflowRun.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input
+
+    @input.setter
+    def input(self, input):
+        """Sets the input of this WorkflowRun.
+
+
+        :param input: The input of this WorkflowRun.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input = input
+
+    @property
+    def output(self):
+        """Gets the output of this WorkflowRun.  # noqa: E501
+
+
+        :return: The output of this WorkflowRun.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._output
+
+    @output.setter
+    def output(self, output):
+        """Sets the output of this WorkflowRun.
+
+
+        :param output: The output of this WorkflowRun.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._output = output
+
+    @property
+    def priority(self):
+        """Gets the priority of this WorkflowRun.  # noqa: E501
+
+
+        :return: The priority of this WorkflowRun.  # noqa: E501
+        :rtype: int
+        """
+        return self._priority
+
+    @priority.setter
+    def priority(self, priority):
+        """Sets the priority of this WorkflowRun.
+
+
+        :param priority: The priority of this WorkflowRun.  # noqa: E501
+        :type: int
+        """
+
+        self._priority = priority
+
+    @property
+    def request_id(self):
+        """Gets the request_id of this WorkflowRun.  # noqa: E501
+
+
+        :return: The request_id of this WorkflowRun.  # noqa: E501
+        :rtype: str
+        """
+        return self._request_id
+
+    @request_id.setter
+    def request_id(self, request_id):
+        """Sets the request_id of this WorkflowRun.
+
+
+        :param request_id: The request_id of this WorkflowRun.  # noqa: E501
+        :type: str
+        """
+
+        self._request_id = request_id
+
+    @property
+    def status(self):
+        """Gets the status of this WorkflowRun.  # noqa: E501
+
+
+        :return: The status of this WorkflowRun.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    def is_completed(self) -> bool:
+        """Checks if the workflow has completed
+        :return: True if the workflow status is COMPLETED, FAILED or TERMINATED
+        """
+        return self._status in terminal_status
+
+    def is_successful(self) -> bool:
+        """Checks if the workflow has completed in successful state (ie COMPLETED)
+        :return: True if the workflow status is COMPLETED
+        """
+        return self._status in successful_status
+
+    @property
+    def reason_for_incompletion(self):
+        return self._reason_for_incompletion
+
+    @status.setter
+    def status(self, status):
+        """Sets the status of this WorkflowRun.
+
+
+        :param status: The status of this WorkflowRun.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            "RUNNING",
+            "COMPLETED",
+            "FAILED",
+            "TIMED_OUT",
+            "TERMINATED",
+            "PAUSED",
+        ]  # noqa: E501
+        if status not in allowed_values:
+            raise ValueError(
+                "Invalid value for `status` ({0}), must be one of {1}".format(  # noqa: E501
+                    status, allowed_values
+                )
+            )
+
+        self._status = status
+
+    def is_successful(self) -> bool:
+        return self.status in successful_status
+
+    def is_completed(self) -> bool:
+        return self.status in terminal_status
+
+    def is_running(self) -> bool:
+        return self.status in running_status
+
+    @property
+    def tasks(self):
+        """Gets the tasks of this WorkflowRun.  # noqa: E501
+
+
+        :return: The tasks of this WorkflowRun.  # noqa: E501
+        :rtype: list[Task]
+        """
+        return self._tasks
+
+    @tasks.setter
+    def tasks(self, tasks):
+        """Sets the tasks of this WorkflowRun.
+
+
+        :param tasks: The tasks of this WorkflowRun.  # noqa: E501
+        :type: list[Task]
+        """
+
+        self._tasks = tasks
+
+    def get_task(self, name: str = None, task_reference_name: str = None) -> Task:
+        if name is None and task_reference_name is None:
+            raise Exception(
+                "ONLY one of name or task_reference_name MUST be provided.  None were provided"
+            )
+        if name is not None and not task_reference_name is None:
+            raise Exception(
+                "ONLY one of name or task_reference_name MUST be provided.  both were provided"
+            )
+
+        current = None
+        for task in self.tasks:
+            if (
+                task.task_def_name == name
+                or task.workflow_task.task_reference_name == task_reference_name
+            ):
+                current = task
+        return current
+
+    @property
+    def update_time(self):
+        """Gets the update_time of this WorkflowRun.  # noqa: E501
+
+
+        :return: The update_time of this WorkflowRun.  # noqa: E501
+        :rtype: int
+        """
+        return self._update_time
+
+    @update_time.setter
+    def update_time(self, update_time):
+        """Sets the update_time of this WorkflowRun.
+
+
+        :param update_time: The update_time of this WorkflowRun.  # noqa: E501
+        :type: int
+        """
+
+        self._update_time = update_time
+
+    @property
+    def variables(self):
+        """Gets the variables of this WorkflowRun.  # noqa: E501
+
+
+        :return: The variables of this WorkflowRun.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._variables
+
+    @variables.setter
+    def variables(self, variables):
+        """Sets the variables of this WorkflowRun.
+
+
+        :param variables: The variables of this WorkflowRun.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._variables = variables
+
+    @property
+    def workflow_id(self):
+        """Gets the workflow_id of this WorkflowRun.  # noqa: E501
+
+
+        :return: The workflow_id of this WorkflowRun.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_id
+
+    @workflow_id.setter
+    def workflow_id(self, workflow_id):
+        """Sets the workflow_id of this WorkflowRun.
+
+
+        :param workflow_id: The workflow_id of this WorkflowRun.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_id = workflow_id
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowRun, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowRun):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
+
+    @property
+    def current_task(self) -> Task:
+        current = None
+        for task in self.tasks:
+            if task.status == "SCHEDULED" or task.status == "IN_PROGRESS":
+                current = task
+        return current
diff --git a/omagent_core/engine/http/models/workflow_schedule.py b/omagent_core/engine/http/models/workflow_schedule.py
new file mode 100644
index 0000000000000000000000000000000000000000..21cbf693388504a6cf163bc9266344c1f547e8c6
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_schedule.py
@@ -0,0 +1,377 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class WorkflowSchedule(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "name": "str",
+        "cron_expression": "str",
+        "run_catchup_schedule_instances": "bool",
+        "paused": "bool",
+        "start_workflow_request": "StartWorkflowRequest",
+        "schedule_start_time": "int",
+        "schedule_end_time": "int",
+        "create_time": "int",
+        "updated_time": "int",
+        "created_by": "str",
+        "updated_by": "str",
+    }
+
+    attribute_map = {
+        "name": "name",
+        "cron_expression": "cronExpression",
+        "run_catchup_schedule_instances": "runCatchupScheduleInstances",
+        "paused": "paused",
+        "start_workflow_request": "startWorkflowRequest",
+        "schedule_start_time": "scheduleStartTime",
+        "schedule_end_time": "scheduleEndTime",
+        "create_time": "createTime",
+        "updated_time": "updatedTime",
+        "created_by": "createdBy",
+        "updated_by": "updatedBy",
+    }
+
+    def __init__(
+        self,
+        name=None,
+        cron_expression=None,
+        run_catchup_schedule_instances=None,
+        paused=None,
+        start_workflow_request=None,
+        schedule_start_time=None,
+        schedule_end_time=None,
+        create_time=None,
+        updated_time=None,
+        created_by=None,
+        updated_by=None,
+    ):  # noqa: E501
+        """WorkflowSchedule - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._cron_expression = None
+        self._run_catchup_schedule_instances = None
+        self._paused = None
+        self._start_workflow_request = None
+        self._schedule_start_time = None
+        self._schedule_end_time = None
+        self._create_time = None
+        self._updated_time = None
+        self._created_by = None
+        self._updated_by = None
+        self.discriminator = None
+        if name is not None:
+            self.name = name
+        if cron_expression is not None:
+            self.cron_expression = cron_expression
+        if run_catchup_schedule_instances is not None:
+            self.run_catchup_schedule_instances = run_catchup_schedule_instances
+        if paused is not None:
+            self.paused = paused
+        if start_workflow_request is not None:
+            self.start_workflow_request = start_workflow_request
+        if schedule_start_time is not None:
+            self.schedule_start_time = schedule_start_time
+        if schedule_end_time is not None:
+            self.schedule_end_time = schedule_end_time
+        if create_time is not None:
+            self.create_time = create_time
+        if updated_time is not None:
+            self.updated_time = updated_time
+        if created_by is not None:
+            self.created_by = created_by
+        if updated_by is not None:
+            self.updated_by = updated_by
+
+    @property
+    def name(self):
+        """Gets the name of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The name of this WorkflowSchedule.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this WorkflowSchedule.
+
+
+        :param name: The name of this WorkflowSchedule.  # noqa: E501
+        :type: str
+        """
+
+        self._name = name
+
+    @property
+    def cron_expression(self):
+        """Gets the cron_expression of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The cron_expression of this WorkflowSchedule.  # noqa: E501
+        :rtype: str
+        """
+        return self._cron_expression
+
+    @cron_expression.setter
+    def cron_expression(self, cron_expression):
+        """Sets the cron_expression of this WorkflowSchedule.
+
+
+        :param cron_expression: The cron_expression of this WorkflowSchedule.  # noqa: E501
+        :type: str
+        """
+
+        self._cron_expression = cron_expression
+
+    @property
+    def run_catchup_schedule_instances(self):
+        """Gets the run_catchup_schedule_instances of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The run_catchup_schedule_instances of this WorkflowSchedule.  # noqa: E501
+        :rtype: bool
+        """
+        return self._run_catchup_schedule_instances
+
+    @run_catchup_schedule_instances.setter
+    def run_catchup_schedule_instances(self, run_catchup_schedule_instances):
+        """Sets the run_catchup_schedule_instances of this WorkflowSchedule.
+
+
+        :param run_catchup_schedule_instances: The run_catchup_schedule_instances of this WorkflowSchedule.  # noqa: E501
+        :type: bool
+        """
+
+        self._run_catchup_schedule_instances = run_catchup_schedule_instances
+
+    @property
+    def paused(self):
+        """Gets the paused of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The paused of this WorkflowSchedule.  # noqa: E501
+        :rtype: bool
+        """
+        return self._paused
+
+    @paused.setter
+    def paused(self, paused):
+        """Sets the paused of this WorkflowSchedule.
+
+
+        :param paused: The paused of this WorkflowSchedule.  # noqa: E501
+        :type: bool
+        """
+
+        self._paused = paused
+
+    @property
+    def start_workflow_request(self):
+        """Gets the start_workflow_request of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The start_workflow_request of this WorkflowSchedule.  # noqa: E501
+        :rtype: StartWorkflowRequest
+        """
+        return self._start_workflow_request
+
+    @start_workflow_request.setter
+    def start_workflow_request(self, start_workflow_request):
+        """Sets the start_workflow_request of this WorkflowSchedule.
+
+
+        :param start_workflow_request: The start_workflow_request of this WorkflowSchedule.  # noqa: E501
+        :type: StartWorkflowRequest
+        """
+
+        self._start_workflow_request = start_workflow_request
+
+    @property
+    def schedule_start_time(self):
+        """Gets the schedule_start_time of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The schedule_start_time of this WorkflowSchedule.  # noqa: E501
+        :rtype: int
+        """
+        return self._schedule_start_time
+
+    @schedule_start_time.setter
+    def schedule_start_time(self, schedule_start_time):
+        """Sets the schedule_start_time of this WorkflowSchedule.
+
+
+        :param schedule_start_time: The schedule_start_time of this WorkflowSchedule.  # noqa: E501
+        :type: int
+        """
+
+        self._schedule_start_time = schedule_start_time
+
+    @property
+    def schedule_end_time(self):
+        """Gets the schedule_end_time of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The schedule_end_time of this WorkflowSchedule.  # noqa: E501
+        :rtype: int
+        """
+        return self._schedule_end_time
+
+    @schedule_end_time.setter
+    def schedule_end_time(self, schedule_end_time):
+        """Sets the schedule_end_time of this WorkflowSchedule.
+
+
+        :param schedule_end_time: The schedule_end_time of this WorkflowSchedule.  # noqa: E501
+        :type: int
+        """
+
+        self._schedule_end_time = schedule_end_time
+
+    @property
+    def create_time(self):
+        """Gets the create_time of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The create_time of this WorkflowSchedule.  # noqa: E501
+        :rtype: int
+        """
+        return self._create_time
+
+    @create_time.setter
+    def create_time(self, create_time):
+        """Sets the create_time of this WorkflowSchedule.
+
+
+        :param create_time: The create_time of this WorkflowSchedule.  # noqa: E501
+        :type: int
+        """
+
+        self._create_time = create_time
+
+    @property
+    def updated_time(self):
+        """Gets the updated_time of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The updated_time of this WorkflowSchedule.  # noqa: E501
+        :rtype: int
+        """
+        return self._updated_time
+
+    @updated_time.setter
+    def updated_time(self, updated_time):
+        """Sets the updated_time of this WorkflowSchedule.
+
+
+        :param updated_time: The updated_time of this WorkflowSchedule.  # noqa: E501
+        :type: int
+        """
+
+        self._updated_time = updated_time
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The created_by of this WorkflowSchedule.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this WorkflowSchedule.
+
+
+        :param created_by: The created_by of this WorkflowSchedule.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def updated_by(self):
+        """Gets the updated_by of this WorkflowSchedule.  # noqa: E501
+
+
+        :return: The updated_by of this WorkflowSchedule.  # noqa: E501
+        :rtype: str
+        """
+        return self._updated_by
+
+    @updated_by.setter
+    def updated_by(self, updated_by):
+        """Sets the updated_by of this WorkflowSchedule.
+
+
+        :param updated_by: The updated_by of this WorkflowSchedule.  # noqa: E501
+        :type: str
+        """
+
+        self._updated_by = updated_by
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowSchedule, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowSchedule):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow_schedule_execution_model.py b/omagent_core/engine/http/models/workflow_schedule_execution_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..f1eb846e020d0a4cc9a59abf087d62e8a56d92c7
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_schedule_execution_model.py
@@ -0,0 +1,357 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class WorkflowScheduleExecutionModel(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "execution_id": "str",
+        "schedule_name": "str",
+        "scheduled_time": "int",
+        "execution_time": "int",
+        "workflow_name": "str",
+        "workflow_id": "str",
+        "reason": "str",
+        "stack_trace": "str",
+        "start_workflow_request": "StartWorkflowRequest",
+        "state": "str",
+    }
+
+    attribute_map = {
+        "execution_id": "executionId",
+        "schedule_name": "scheduleName",
+        "scheduled_time": "scheduledTime",
+        "execution_time": "executionTime",
+        "workflow_name": "workflowName",
+        "workflow_id": "workflowId",
+        "reason": "reason",
+        "stack_trace": "stackTrace",
+        "start_workflow_request": "startWorkflowRequest",
+        "state": "state",
+    }
+
+    def __init__(
+        self,
+        execution_id=None,
+        schedule_name=None,
+        scheduled_time=None,
+        execution_time=None,
+        workflow_name=None,
+        workflow_id=None,
+        reason=None,
+        stack_trace=None,
+        start_workflow_request=None,
+        state=None,
+    ):  # noqa: E501
+        """WorkflowScheduleExecutionModel - a model defined in Swagger"""  # noqa: E501
+        self._execution_id = None
+        self._schedule_name = None
+        self._scheduled_time = None
+        self._execution_time = None
+        self._workflow_name = None
+        self._workflow_id = None
+        self._reason = None
+        self._stack_trace = None
+        self._start_workflow_request = None
+        self._state = None
+        self.discriminator = None
+        if execution_id is not None:
+            self.execution_id = execution_id
+        if schedule_name is not None:
+            self.schedule_name = schedule_name
+        if scheduled_time is not None:
+            self.scheduled_time = scheduled_time
+        if execution_time is not None:
+            self.execution_time = execution_time
+        if workflow_name is not None:
+            self.workflow_name = workflow_name
+        if workflow_id is not None:
+            self.workflow_id = workflow_id
+        if reason is not None:
+            self.reason = reason
+        if stack_trace is not None:
+            self.stack_trace = stack_trace
+        if start_workflow_request is not None:
+            self.start_workflow_request = start_workflow_request
+        if state is not None:
+            self.state = state
+
+    @property
+    def execution_id(self):
+        """Gets the execution_id of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The execution_id of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: str
+        """
+        return self._execution_id
+
+    @execution_id.setter
+    def execution_id(self, execution_id):
+        """Sets the execution_id of this WorkflowScheduleExecutionModel.
+
+
+        :param execution_id: The execution_id of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: str
+        """
+
+        self._execution_id = execution_id
+
+    @property
+    def schedule_name(self):
+        """Gets the schedule_name of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The schedule_name of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: str
+        """
+        return self._schedule_name
+
+    @schedule_name.setter
+    def schedule_name(self, schedule_name):
+        """Sets the schedule_name of this WorkflowScheduleExecutionModel.
+
+
+        :param schedule_name: The schedule_name of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: str
+        """
+
+        self._schedule_name = schedule_name
+
+    @property
+    def scheduled_time(self):
+        """Gets the scheduled_time of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The scheduled_time of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: int
+        """
+        return self._scheduled_time
+
+    @scheduled_time.setter
+    def scheduled_time(self, scheduled_time):
+        """Sets the scheduled_time of this WorkflowScheduleExecutionModel.
+
+
+        :param scheduled_time: The scheduled_time of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: int
+        """
+
+        self._scheduled_time = scheduled_time
+
+    @property
+    def execution_time(self):
+        """Gets the execution_time of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The execution_time of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: int
+        """
+        return self._execution_time
+
+    @execution_time.setter
+    def execution_time(self, execution_time):
+        """Sets the execution_time of this WorkflowScheduleExecutionModel.
+
+
+        :param execution_time: The execution_time of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: int
+        """
+
+        self._execution_time = execution_time
+
+    @property
+    def workflow_name(self):
+        """Gets the workflow_name of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The workflow_name of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_name
+
+    @workflow_name.setter
+    def workflow_name(self, workflow_name):
+        """Sets the workflow_name of this WorkflowScheduleExecutionModel.
+
+
+        :param workflow_name: The workflow_name of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_name = workflow_name
+
+    @property
+    def workflow_id(self):
+        """Gets the workflow_id of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The workflow_id of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_id
+
+    @workflow_id.setter
+    def workflow_id(self, workflow_id):
+        """Sets the workflow_id of this WorkflowScheduleExecutionModel.
+
+
+        :param workflow_id: The workflow_id of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_id = workflow_id
+
+    @property
+    def reason(self):
+        """Gets the reason of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The reason of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: str
+        """
+        return self._reason
+
+    @reason.setter
+    def reason(self, reason):
+        """Sets the reason of this WorkflowScheduleExecutionModel.
+
+
+        :param reason: The reason of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: str
+        """
+
+        self._reason = reason
+
+    @property
+    def stack_trace(self):
+        """Gets the stack_trace of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The stack_trace of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: str
+        """
+        return self._stack_trace
+
+    @stack_trace.setter
+    def stack_trace(self, stack_trace):
+        """Sets the stack_trace of this WorkflowScheduleExecutionModel.
+
+
+        :param stack_trace: The stack_trace of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: str
+        """
+
+        self._stack_trace = stack_trace
+
+    @property
+    def start_workflow_request(self):
+        """Gets the start_workflow_request of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The start_workflow_request of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: StartWorkflowRequest
+        """
+        return self._start_workflow_request
+
+    @start_workflow_request.setter
+    def start_workflow_request(self, start_workflow_request):
+        """Sets the start_workflow_request of this WorkflowScheduleExecutionModel.
+
+
+        :param start_workflow_request: The start_workflow_request of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: StartWorkflowRequest
+        """
+
+        self._start_workflow_request = start_workflow_request
+
+    @property
+    def state(self):
+        """Gets the state of this WorkflowScheduleExecutionModel.  # noqa: E501
+
+
+        :return: The state of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :rtype: str
+        """
+        return self._state
+
+    @state.setter
+    def state(self, state):
+        """Sets the state of this WorkflowScheduleExecutionModel.
+
+
+        :param state: The state of this WorkflowScheduleExecutionModel.  # noqa: E501
+        :type: str
+        """
+        allowed_values = ["POLLED", "FAILED", "EXECUTED"]  # noqa: E501
+        if state not in allowed_values:
+            raise ValueError(
+                "Invalid value for `state` ({0}), must be one of {1}".format(  # noqa: E501
+                    state, allowed_values
+                )
+            )
+
+        self._state = state
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowScheduleExecutionModel, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowScheduleExecutionModel):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow_state_update.py b/omagent_core/engine/http/models/workflow_state_update.py
new file mode 100644
index 0000000000000000000000000000000000000000..2530d715f588962f12c33e8fcc9e5da5a2c311d0
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_state_update.py
@@ -0,0 +1,161 @@
+import pprint
+import re  # noqa: F401
+
+import six
+from omagent_core.engine.http.models import TaskResult
+
+
+class WorkflowStateUpdate(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "task_reference_name": "str",
+        "task_result": "TaskResult",
+        "variables": "dict(str, object)",
+    }
+
+    attribute_map = {
+        "task_reference_name": "taskReferenceName",
+        "task_result": "taskResult",
+        "variables": "variables",
+    }
+
+    def __init__(
+        self,
+        task_reference_name: str = None,
+        task_result: TaskResult = None,
+        variables: dict[str, object] = None,
+    ):  # noqa: E501
+        """WorkflowStateUpdate - a model defined in Swagger"""  # noqa: E501
+        self._task_reference_name = None
+        self._task_result = None
+        self._variables = None
+        if task_reference_name is not None:
+            self.task_reference_name = task_reference_name
+        if task_result is not None:
+            self.task_result = task_result
+        if variables is not None:
+            self.variables = variables
+
+    @property
+    def task_reference_name(self) -> str:
+        """Gets the task_reference_name of this WorkflowStateUpdate.  # noqa: E501
+
+
+        :return: The task_reference_name of this WorkflowStateUpdate.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_reference_name
+
+    @task_reference_name.setter
+    def task_reference_name(self, task_reference_name: str):
+        """Sets the task_reference_name of this WorkflowStateUpdate.
+
+
+        :param task_reference_name: The task_reference_name of this WorkflowStateUpdate.  # noqa: E501
+        :type: str
+        """
+
+        self._task_reference_name = task_reference_name
+
+    @property
+    def task_result(self) -> TaskResult:
+        """Gets the task_result of this WorkflowStateUpdate.  # noqa: E501
+
+
+        :return: The task_result of this WorkflowStateUpdate.  # noqa: E501
+        :rtype: TaskResult
+        """
+        return self._task_result
+
+    @task_result.setter
+    def task_result(self, task_result: TaskResult):
+        """Sets the task_result of this WorkflowStateUpdate.
+
+
+        :param task_result: The task_result of this WorkflowStateUpdate.  # noqa: E501
+        :type: TaskResult
+        """
+
+        self._task_result = task_result
+
+    @property
+    def variables(self) -> dict[str, object]:
+        """Gets the variables of this WorkflowStateUpdate.  # noqa: E501
+
+
+        :return: The variables of this WorkflowStateUpdate.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._variables
+
+    @variables.setter
+    def variables(self, variables: dict[str, object]):
+        """Sets the variables of this WorkflowStateUpdate.
+
+
+        :param variables: The variables of this WorkflowStateUpdate.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._variables = variables
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowStateUpdate, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowStateUpdate):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow_status.py b/omagent_core/engine/http/models/workflow_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..0de9c0aac8137c80e259ce244114db10f7bf4302
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_status.py
@@ -0,0 +1,248 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+terminal_status = ("COMPLETED", "FAILED", "TIMED_OUT", "TERMINATED")
+successful_status = ("PAUSED", "COMPLETED")
+running_status = ("RUNNING", "PAUSED")
+
+
+class WorkflowStatus(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "workflow_id": "str",
+        "correlation_id": "str",
+        "output": "dict(str, object)",
+        "variables": "dict(str, object)",
+        "status": "str",
+    }
+
+    attribute_map = {
+        "workflow_id": "workflowId",
+        "correlation_id": "correlationId",
+        "output": "output",
+        "variables": "variables",
+        "status": "status",
+    }
+
+    def __init__(
+        self,
+        workflow_id=None,
+        correlation_id=None,
+        output=None,
+        variables=None,
+        status=None,
+    ):  # noqa: E501
+        """WorkflowStatus - a model defined in Swagger"""  # noqa: E501
+        self._workflow_id = None
+        self._correlation_id = None
+        self._output = None
+        self._variables = None
+        self._status = None
+        self.discriminator = None
+        if workflow_id is not None:
+            self.workflow_id = workflow_id
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if output is not None:
+            self.output = output
+        if variables is not None:
+            self.variables = variables
+        if status is not None:
+            self.status = status
+
+    @property
+    def workflow_id(self):
+        """Gets the workflow_id of this WorkflowStatus.  # noqa: E501
+
+
+        :return: The workflow_id of this WorkflowStatus.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_id
+
+    @workflow_id.setter
+    def workflow_id(self, workflow_id):
+        """Sets the workflow_id of this WorkflowStatus.
+
+
+        :param workflow_id: The workflow_id of this WorkflowStatus.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_id = workflow_id
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this WorkflowStatus.  # noqa: E501
+
+
+        :return: The correlation_id of this WorkflowStatus.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this WorkflowStatus.
+
+
+        :param correlation_id: The correlation_id of this WorkflowStatus.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def output(self):
+        """Gets the output of this WorkflowStatus.  # noqa: E501
+
+
+        :return: The output of this WorkflowStatus.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._output
+
+    @output.setter
+    def output(self, output):
+        """Sets the output of this WorkflowStatus.
+
+
+        :param output: The output of this WorkflowStatus.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._output = output
+
+    @property
+    def variables(self):
+        """Gets the variables of this WorkflowStatus.  # noqa: E501
+
+
+        :return: The variables of this WorkflowStatus.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._variables
+
+    @variables.setter
+    def variables(self, variables):
+        """Sets the variables of this WorkflowStatus.
+
+
+        :param variables: The variables of this WorkflowStatus.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._variables = variables
+
+    @property
+    def status(self):
+        """Gets the status of this WorkflowStatus.  # noqa: E501
+
+
+        :return: The status of this WorkflowStatus.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    @status.setter
+    def status(self, status):
+        """Sets the status of this WorkflowStatus.
+
+
+        :param status: The status of this WorkflowStatus.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            "RUNNING",
+            "COMPLETED",
+            "FAILED",
+            "TIMED_OUT",
+            "TERMINATED",
+            "PAUSED",
+        ]  # noqa: E501
+        if status not in allowed_values:
+            raise ValueError(
+                "Invalid value for `status` ({0}), must be one of {1}".format(  # noqa: E501
+                    status, allowed_values
+                )
+            )
+
+        self._status = status
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowStatus, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def is_completed(self) -> bool:
+        """Checks if the workflow has completed
+        :return: True if the workflow status is COMPLETED, FAILED or TERMINATED
+        """
+        return self._status in terminal_status
+
+    def is_successful(self) -> bool:
+        """Checks if the workflow has completed in successful state (ie COMPLETED)
+        :return: True if the workflow status is COMPLETED
+        """
+        return self._status in successful_status
+
+    def is_running(self) -> bool:
+        return self.status in running_status
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowStatus):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow_summary.py b/omagent_core/engine/http/models/workflow_summary.py
new file mode 100644
index 0000000000000000000000000000000000000000..75ea593137a355b79f5422ddeeecd10f4bedd798
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_summary.py
@@ -0,0 +1,642 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class WorkflowSummary(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "workflow_type": "str",
+        "version": "int",
+        "workflow_id": "str",
+        "correlation_id": "str",
+        "start_time": "str",
+        "update_time": "str",
+        "end_time": "str",
+        "status": "str",
+        "input": "str",
+        "output": "str",
+        "reason_for_incompletion": "str",
+        "execution_time": "int",
+        "event": "str",
+        "failed_reference_task_names": "str",
+        "external_input_payload_storage_path": "str",
+        "external_output_payload_storage_path": "str",
+        "priority": "int",
+        "created_by": "str",
+        "output_size": "int",
+        "input_size": "int",
+    }
+
+    attribute_map = {
+        "workflow_type": "workflowType",
+        "version": "version",
+        "workflow_id": "workflowId",
+        "correlation_id": "correlationId",
+        "start_time": "startTime",
+        "update_time": "updateTime",
+        "end_time": "endTime",
+        "status": "status",
+        "input": "input",
+        "output": "output",
+        "reason_for_incompletion": "reasonForIncompletion",
+        "execution_time": "executionTime",
+        "event": "event",
+        "failed_reference_task_names": "failedReferenceTaskNames",
+        "external_input_payload_storage_path": "externalInputPayloadStoragePath",
+        "external_output_payload_storage_path": "externalOutputPayloadStoragePath",
+        "priority": "priority",
+        "created_by": "createdBy",
+        "output_size": "outputSize",
+        "input_size": "inputSize",
+    }
+
+    def __init__(
+        self,
+        workflow_type=None,
+        version=None,
+        workflow_id=None,
+        correlation_id=None,
+        start_time=None,
+        update_time=None,
+        end_time=None,
+        status=None,
+        input=None,
+        output=None,
+        reason_for_incompletion=None,
+        execution_time=None,
+        event=None,
+        failed_reference_task_names=None,
+        external_input_payload_storage_path=None,
+        external_output_payload_storage_path=None,
+        priority=None,
+        created_by=None,
+        output_size=None,
+        input_size=None,
+    ):  # noqa: E501
+        """WorkflowSummary - a model defined in Swagger"""  # noqa: E501
+        self._workflow_type = None
+        self._version = None
+        self._workflow_id = None
+        self._correlation_id = None
+        self._start_time = None
+        self._update_time = None
+        self._end_time = None
+        self._status = None
+        self._input = None
+        self._output = None
+        self._reason_for_incompletion = None
+        self._execution_time = None
+        self._event = None
+        self._failed_reference_task_names = None
+        self._external_input_payload_storage_path = None
+        self._external_output_payload_storage_path = None
+        self._priority = None
+        self._created_by = None
+        self._output_size = None
+        self._input_size = None
+        self.discriminator = None
+        if workflow_type is not None:
+            self.workflow_type = workflow_type
+        if version is not None:
+            self.version = version
+        if workflow_id is not None:
+            self.workflow_id = workflow_id
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if start_time is not None:
+            self.start_time = start_time
+        if update_time is not None:
+            self.update_time = update_time
+        if end_time is not None:
+            self.end_time = end_time
+        if status is not None:
+            self.status = status
+        if input is not None:
+            self.input = input
+        if output is not None:
+            self.output = output
+        if reason_for_incompletion is not None:
+            self.reason_for_incompletion = reason_for_incompletion
+        if execution_time is not None:
+            self.execution_time = execution_time
+        if event is not None:
+            self.event = event
+        if failed_reference_task_names is not None:
+            self.failed_reference_task_names = failed_reference_task_names
+        if external_input_payload_storage_path is not None:
+            self.external_input_payload_storage_path = (
+                external_input_payload_storage_path
+            )
+        if external_output_payload_storage_path is not None:
+            self.external_output_payload_storage_path = (
+                external_output_payload_storage_path
+            )
+        if priority is not None:
+            self.priority = priority
+        if created_by is not None:
+            self.created_by = created_by
+        if output_size is not None:
+            self.output_size = output_size
+        if input_size is not None:
+            self.input_size = input_size
+
+    @property
+    def workflow_type(self):
+        """Gets the workflow_type of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The workflow_type of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_type
+
+    @workflow_type.setter
+    def workflow_type(self, workflow_type):
+        """Sets the workflow_type of this WorkflowSummary.
+
+
+        :param workflow_type: The workflow_type of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_type = workflow_type
+
+    @property
+    def version(self):
+        """Gets the version of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The version of this WorkflowSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._version
+
+    @version.setter
+    def version(self, version):
+        """Sets the version of this WorkflowSummary.
+
+
+        :param version: The version of this WorkflowSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._version = version
+
+    @property
+    def workflow_id(self):
+        """Gets the workflow_id of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The workflow_id of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_id
+
+    @workflow_id.setter
+    def workflow_id(self, workflow_id):
+        """Sets the workflow_id of this WorkflowSummary.
+
+
+        :param workflow_id: The workflow_id of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._workflow_id = workflow_id
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The correlation_id of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this WorkflowSummary.
+
+
+        :param correlation_id: The correlation_id of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def start_time(self):
+        """Gets the start_time of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The start_time of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._start_time
+
+    @start_time.setter
+    def start_time(self, start_time):
+        """Sets the start_time of this WorkflowSummary.
+
+
+        :param start_time: The start_time of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._start_time = start_time
+
+    @property
+    def update_time(self):
+        """Gets the update_time of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The update_time of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._update_time
+
+    @update_time.setter
+    def update_time(self, update_time):
+        """Sets the update_time of this WorkflowSummary.
+
+
+        :param update_time: The update_time of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._update_time = update_time
+
+    @property
+    def end_time(self):
+        """Gets the end_time of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The end_time of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._end_time
+
+    @end_time.setter
+    def end_time(self, end_time):
+        """Sets the end_time of this WorkflowSummary.
+
+
+        :param end_time: The end_time of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._end_time = end_time
+
+    @property
+    def status(self):
+        """Gets the status of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The status of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    @status.setter
+    def status(self, status):
+        """Sets the status of this WorkflowSummary.
+
+
+        :param status: The status of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+        allowed_values = [
+            "RUNNING",
+            "COMPLETED",
+            "FAILED",
+            "TIMED_OUT",
+            "TERMINATED",
+            "PAUSED",
+        ]  # noqa: E501
+        if status not in allowed_values:
+            raise ValueError(
+                "Invalid value for `status` ({0}), must be one of {1}".format(  # noqa: E501
+                    status, allowed_values
+                )
+            )
+
+        self._status = status
+
+    @property
+    def input(self):
+        """Gets the input of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The input of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._input
+
+    @input.setter
+    def input(self, input):
+        """Sets the input of this WorkflowSummary.
+
+
+        :param input: The input of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._input = input
+
+    @property
+    def output(self):
+        """Gets the output of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The output of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._output
+
+    @output.setter
+    def output(self, output):
+        """Sets the output of this WorkflowSummary.
+
+
+        :param output: The output of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._output = output
+
+    @property
+    def reason_for_incompletion(self):
+        """Gets the reason_for_incompletion of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The reason_for_incompletion of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._reason_for_incompletion
+
+    @reason_for_incompletion.setter
+    def reason_for_incompletion(self, reason_for_incompletion):
+        """Sets the reason_for_incompletion of this WorkflowSummary.
+
+
+        :param reason_for_incompletion: The reason_for_incompletion of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._reason_for_incompletion = reason_for_incompletion
+
+    @property
+    def execution_time(self):
+        """Gets the execution_time of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The execution_time of this WorkflowSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._execution_time
+
+    @execution_time.setter
+    def execution_time(self, execution_time):
+        """Sets the execution_time of this WorkflowSummary.
+
+
+        :param execution_time: The execution_time of this WorkflowSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._execution_time = execution_time
+
+    @property
+    def event(self):
+        """Gets the event of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The event of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._event
+
+    @event.setter
+    def event(self, event):
+        """Sets the event of this WorkflowSummary.
+
+
+        :param event: The event of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._event = event
+
+    @property
+    def failed_reference_task_names(self):
+        """Gets the failed_reference_task_names of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The failed_reference_task_names of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._failed_reference_task_names
+
+    @failed_reference_task_names.setter
+    def failed_reference_task_names(self, failed_reference_task_names):
+        """Sets the failed_reference_task_names of this WorkflowSummary.
+
+
+        :param failed_reference_task_names: The failed_reference_task_names of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._failed_reference_task_names = failed_reference_task_names
+
+    @property
+    def external_input_payload_storage_path(self):
+        """Gets the external_input_payload_storage_path of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The external_input_payload_storage_path of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_input_payload_storage_path
+
+    @external_input_payload_storage_path.setter
+    def external_input_payload_storage_path(self, external_input_payload_storage_path):
+        """Sets the external_input_payload_storage_path of this WorkflowSummary.
+
+
+        :param external_input_payload_storage_path: The external_input_payload_storage_path of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._external_input_payload_storage_path = external_input_payload_storage_path
+
+    @property
+    def external_output_payload_storage_path(self):
+        """Gets the external_output_payload_storage_path of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The external_output_payload_storage_path of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_output_payload_storage_path
+
+    @external_output_payload_storage_path.setter
+    def external_output_payload_storage_path(
+        self, external_output_payload_storage_path
+    ):
+        """Sets the external_output_payload_storage_path of this WorkflowSummary.
+
+
+        :param external_output_payload_storage_path: The external_output_payload_storage_path of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._external_output_payload_storage_path = (
+            external_output_payload_storage_path
+        )
+
+    @property
+    def priority(self):
+        """Gets the priority of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The priority of this WorkflowSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._priority
+
+    @priority.setter
+    def priority(self, priority):
+        """Sets the priority of this WorkflowSummary.
+
+
+        :param priority: The priority of this WorkflowSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._priority = priority
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The created_by of this WorkflowSummary.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this WorkflowSummary.
+
+
+        :param created_by: The created_by of this WorkflowSummary.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def output_size(self):
+        """Gets the output_size of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The output_size of this WorkflowSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._output_size
+
+    @output_size.setter
+    def output_size(self, output_size):
+        """Sets the output_size of this WorkflowSummary.
+
+
+        :param output_size: The output_size of this WorkflowSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._output_size = output_size
+
+    @property
+    def input_size(self):
+        """Gets the input_size of this WorkflowSummary.  # noqa: E501
+
+
+        :return: The input_size of this WorkflowSummary.  # noqa: E501
+        :rtype: int
+        """
+        return self._input_size
+
+    @input_size.setter
+    def input_size(self, input_size):
+        """Sets the input_size of this WorkflowSummary.
+
+
+        :param input_size: The input_size of this WorkflowSummary.  # noqa: E501
+        :type: int
+        """
+
+        self._input_size = input_size
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowSummary, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowSummary):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow_tag.py b/omagent_core/engine/http/models/workflow_tag.py
new file mode 100644
index 0000000000000000000000000000000000000000..e61348d4c132c945af092681a2274f677329fb2f
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_tag.py
@@ -0,0 +1,100 @@
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class WorkflowTag(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {"rate_limit": "RateLimit"}
+
+    attribute_map = {"rate_limit": "rateLimit"}
+
+    def __init__(self, rate_limit=None):  # noqa: E501
+        """WorkflowTag - a model defined in Swagger"""  # noqa: E501
+        self._rate_limit = None
+        self.discriminator = None
+        if rate_limit is not None:
+            self.rate_limit = rate_limit
+
+    @property
+    def rate_limit(self):
+        """Gets the rate_limit of this WorkflowTag.  # noqa: E501
+
+
+        :return: The rate_limit of this WorkflowTag.  # noqa: E501
+        :rtype: RateLimit
+        """
+        return self._rate_limit
+
+    @rate_limit.setter
+    def rate_limit(self, rate_limit):
+        """Sets the rate_limit of this WorkflowTag.
+
+
+        :param rate_limit: The rate_limit of this WorkflowTag.  # noqa: E501
+        :type: RateLimit
+        """
+
+        self._rate_limit = rate_limit
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowTag, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowTag):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow_task.py b/omagent_core/engine/http/models/workflow_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..1232bac277abd3bd9744c7463295b76d2f681c3a
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_task.py
@@ -0,0 +1,939 @@
+import pprint
+import re  # noqa: F401
+from typing import List
+
+import six
+from omagent_core.engine.http.models.state_change_event import (
+    StateChangeConfig, StateChangeEvent, StateChangeEventType)
+
+
+class CacheConfig(object):
+    swagger_types = {"key": "str", "ttl_in_second": "int"}
+
+    attribute_map = {"key": "key", "ttl_in_second": "ttlInSecond"}
+
+    def __init__(self, key: str, ttl_in_second: int):
+        self._key = key
+        self._ttl_in_second = ttl_in_second
+
+    @property
+    def key(self):
+        return self._key
+
+    @key.setter
+    def key(self, key):
+        self._key = key
+
+    @property
+    def ttl_in_second(self):
+        return self._ttl_in_second
+
+    @ttl_in_second.setter
+    def ttl_in_second(self, ttl_in_second):
+        self._ttl_in_second = ttl_in_second
+
+
+class WorkflowTask(object):
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+
+    swagger_types = {
+        "name": "str",
+        "task_reference_name": "str",
+        "description": "str",
+        "input_parameters": "dict(str, object)",
+        "type": "str",
+        "dynamic_task_name_param": "str",
+        "case_value_param": "str",
+        "case_expression": "str",
+        "script_expression": "str",
+        "decision_cases": "dict(str, list[WorkflowTask])",
+        "dynamic_fork_join_tasks_param": "str",
+        "dynamic_fork_tasks_param": "str",
+        "dynamic_fork_tasks_input_param_name": "str",
+        "default_case": "list[WorkflowTask]",
+        "fork_tasks": "list[list[WorkflowTask]]",
+        "start_delay": "int",
+        "sub_workflow_param": "SubWorkflowParams",
+        "join_on": "list[str]",
+        "sink": "str",
+        "optional": "bool",
+        "task_definition": "TaskDef",
+        "rate_limited": "bool",
+        "default_exclusive_join_task": "list[str]",
+        "async_complete": "bool",
+        "loop_condition": "str",
+        "loop_over": "list[WorkflowTask]",
+        "retry_count": "int",
+        "evaluator_type": "str",
+        "expression": "str",
+        "workflow_task_type": "str",
+        "on_state_change": "dict(str, StateChangeConfig)",
+        "cache_config": "CacheConfig",
+    }
+
+    attribute_map = {
+        "name": "name",
+        "task_reference_name": "taskReferenceName",
+        "description": "description",
+        "input_parameters": "inputParameters",
+        "type": "type",
+        "dynamic_task_name_param": "dynamicTaskNameParam",
+        "case_value_param": "caseValueParam",
+        "case_expression": "caseExpression",
+        "script_expression": "scriptExpression",
+        "decision_cases": "decisionCases",
+        "dynamic_fork_join_tasks_param": "dynamicForkTasksParam",
+        "dynamic_fork_tasks_param": "dynamicForkTasksParam",
+        "dynamic_fork_tasks_input_param_name": "dynamicForkTasksInputParamName",
+        "default_case": "defaultCase",
+        "fork_tasks": "forkTasks",
+        "start_delay": "startDelay",
+        "sub_workflow_param": "subWorkflowParam",
+        "join_on": "joinOn",
+        "sink": "sink",
+        "optional": "optional",
+        "task_definition": "taskDefinition",
+        "rate_limited": "rateLimited",
+        "default_exclusive_join_task": "defaultExclusiveJoinTask",
+        "async_complete": "asyncComplete",
+        "loop_condition": "loopCondition",
+        "loop_over": "loopOver",
+        "retry_count": "retryCount",
+        "evaluator_type": "evaluatorType",
+        "expression": "expression",
+        "workflow_task_type": "workflowTaskType",
+        "on_state_change": "onStateChange",
+        "cache_config": "cacheConfig",
+    }
+
+    def __init__(
+        self,
+        name=None,
+        task_reference_name=None,
+        description=None,
+        input_parameters=None,
+        type=None,
+        dynamic_task_name_param=None,
+        case_value_param=None,
+        case_expression=None,
+        script_expression=None,
+        decision_cases=None,
+        dynamic_fork_join_tasks_param=None,
+        dynamic_fork_tasks_param=None,
+        dynamic_fork_tasks_input_param_name=None,
+        default_case=None,
+        fork_tasks=None,
+        start_delay=None,
+        sub_workflow_param=None,
+        join_on=None,
+        sink=None,
+        optional=None,
+        task_definition=None,
+        rate_limited=None,
+        default_exclusive_join_task=None,
+        async_complete=None,
+        loop_condition=None,
+        loop_over=None,
+        retry_count=None,
+        evaluator_type=None,
+        expression=None,
+        workflow_task_type=None,
+        on_state_change: dict[str, StateChangeConfig] = None,
+        cache_config: CacheConfig = None,
+    ):  # noqa: E501
+        """WorkflowTask - a model defined in Swagger"""  # noqa: E501
+        self._name = None
+        self._task_reference_name = None
+        self._description = None
+        self._input_parameters = None
+        self._type = None
+        self._dynamic_task_name_param = None
+        self._case_value_param = None
+        self._case_expression = None
+        self._script_expression = None
+        self._decision_cases = None
+        self._dynamic_fork_join_tasks_param = None
+        self._dynamic_fork_tasks_param = None
+        self._dynamic_fork_tasks_input_param_name = None
+        self._default_case = None
+        self._fork_tasks = None
+        self._start_delay = None
+        self._sub_workflow_param = None
+        self._join_on = None
+        self._sink = None
+        self._optional = None
+        self._task_definition = None
+        self._rate_limited = None
+        self._default_exclusive_join_task = None
+        self._async_complete = None
+        self._loop_condition = None
+        self._loop_over = None
+        self._retry_count = None
+        self._evaluator_type = None
+        self._expression = None
+        self._workflow_task_type = None
+        self.discriminator = None
+        self._on_state_change = None
+        self._cache_config = None
+        self.name = name
+        self.task_reference_name = task_reference_name
+        if description is not None:
+            self.description = description
+        if input_parameters is not None:
+            self.input_parameters = input_parameters
+        if type is not None:
+            self.type = type
+        if dynamic_task_name_param is not None:
+            self.dynamic_task_name_param = dynamic_task_name_param
+        if case_value_param is not None:
+            self.case_value_param = case_value_param
+        if case_expression is not None:
+            self.case_expression = case_expression
+        if script_expression is not None:
+            self.script_expression = script_expression
+        if decision_cases is not None:
+            self.decision_cases = decision_cases
+        if dynamic_fork_join_tasks_param is not None:
+            self.dynamic_fork_join_tasks_param = dynamic_fork_join_tasks_param
+        if dynamic_fork_tasks_param is not None:
+            self.dynamic_fork_tasks_param = dynamic_fork_tasks_param
+        if dynamic_fork_tasks_input_param_name is not None:
+            self.dynamic_fork_tasks_input_param_name = (
+                dynamic_fork_tasks_input_param_name
+            )
+        if default_case is not None:
+            self.default_case = default_case
+        if fork_tasks is not None:
+            self.fork_tasks = fork_tasks
+        if start_delay is not None:
+            self.start_delay = start_delay
+        if sub_workflow_param is not None:
+            self.sub_workflow_param = sub_workflow_param
+        if join_on is not None:
+            self.join_on = join_on
+        if sink is not None:
+            self.sink = sink
+        if optional is not None:
+            self.optional = optional
+        if task_definition is not None:
+            self.task_definition = task_definition
+        if rate_limited is not None:
+            self.rate_limited = rate_limited
+        if default_exclusive_join_task is not None:
+            self.default_exclusive_join_task = default_exclusive_join_task
+        if async_complete is not None:
+            self.async_complete = async_complete
+        if loop_condition is not None:
+            self.loop_condition = loop_condition
+        if loop_over is not None:
+            self.loop_over = loop_over
+        if retry_count is not None:
+            self.retry_count = retry_count
+        if evaluator_type is not None:
+            self.evaluator_type = evaluator_type
+        if expression is not None:
+            self.expression = expression
+        if workflow_task_type is not None:
+            self.workflow_task_type = workflow_task_type
+        if on_state_change is not None:
+            self._on_state_change = on_state_change
+        self._cache_config = cache_config
+
+    @property
+    def name(self):
+        """Gets the name of this WorkflowTask.  # noqa: E501
+
+
+        :return: The name of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this WorkflowTask.
+
+
+        :param name: The name of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+        self._name = name
+
+    @property
+    def task_reference_name(self):
+        """Gets the task_reference_name of this WorkflowTask.  # noqa: E501
+
+
+        :return: The task_reference_name of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._task_reference_name
+
+    @task_reference_name.setter
+    def task_reference_name(self, task_reference_name):
+        """Sets the task_reference_name of this WorkflowTask.
+
+
+        :param task_reference_name: The task_reference_name of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+        self._task_reference_name = task_reference_name
+
+    @property
+    def description(self):
+        """Gets the description of this WorkflowTask.  # noqa: E501
+
+
+        :return: The description of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._description
+
+    @description.setter
+    def description(self, description):
+        """Sets the description of this WorkflowTask.
+
+
+        :param description: The description of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._description = description
+
+    @property
+    def input_parameters(self):
+        """Gets the input_parameters of this WorkflowTask.  # noqa: E501
+
+
+        :return: The input_parameters of this WorkflowTask.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input_parameters
+
+    @input_parameters.setter
+    def input_parameters(self, input_parameters):
+        """Sets the input_parameters of this WorkflowTask.
+
+
+        :param input_parameters: The input_parameters of this WorkflowTask.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input_parameters = input_parameters
+
+    @property
+    def type(self):
+        """Gets the type of this WorkflowTask.  # noqa: E501
+
+
+        :return: The type of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._type
+
+    @type.setter
+    def type(self, type):
+        """Sets the type of this WorkflowTask.
+
+
+        :param type: The type of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._type = type
+
+    @property
+    def dynamic_task_name_param(self):
+        """Gets the dynamic_task_name_param of this WorkflowTask.  # noqa: E501
+
+
+        :return: The dynamic_task_name_param of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._dynamic_task_name_param
+
+    @dynamic_task_name_param.setter
+    def dynamic_task_name_param(self, dynamic_task_name_param):
+        """Sets the dynamic_task_name_param of this WorkflowTask.
+
+
+        :param dynamic_task_name_param: The dynamic_task_name_param of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._dynamic_task_name_param = dynamic_task_name_param
+
+    @property
+    def case_value_param(self):
+        """Gets the case_value_param of this WorkflowTask.  # noqa: E501
+
+
+        :return: The case_value_param of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._case_value_param
+
+    @case_value_param.setter
+    def case_value_param(self, case_value_param):
+        """Sets the case_value_param of this WorkflowTask.
+
+
+        :param case_value_param: The case_value_param of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._case_value_param = case_value_param
+
+    @property
+    def case_expression(self):
+        """Gets the case_expression of this WorkflowTask.  # noqa: E501
+
+
+        :return: The case_expression of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._case_expression
+
+    @case_expression.setter
+    def case_expression(self, case_expression):
+        """Sets the case_expression of this WorkflowTask.
+
+
+        :param case_expression: The case_expression of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._case_expression = case_expression
+
+    @property
+    def script_expression(self):
+        """Gets the script_expression of this WorkflowTask.  # noqa: E501
+
+
+        :return: The script_expression of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._script_expression
+
+    @script_expression.setter
+    def script_expression(self, script_expression):
+        """Sets the script_expression of this WorkflowTask.
+
+
+        :param script_expression: The script_expression of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._script_expression = script_expression
+
+    @property
+    def decision_cases(self):
+        """Gets the decision_cases of this WorkflowTask.  # noqa: E501
+
+
+        :return: The decision_cases of this WorkflowTask.  # noqa: E501
+        :rtype: dict(str, list[WorkflowTask])
+        """
+        return self._decision_cases
+
+    @decision_cases.setter
+    def decision_cases(self, decision_cases):
+        """Sets the decision_cases of this WorkflowTask.
+
+
+        :param decision_cases: The decision_cases of this WorkflowTask.  # noqa: E501
+        :type: dict(str, list[WorkflowTask])
+        """
+
+        self._decision_cases = decision_cases
+
+    @property
+    def dynamic_fork_join_tasks_param(self):
+        """Gets the dynamic_fork_join_tasks_param of this WorkflowTask.  # noqa: E501
+
+
+        :return: The dynamic_fork_join_tasks_param of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._dynamic_fork_join_tasks_param
+
+    @dynamic_fork_join_tasks_param.setter
+    def dynamic_fork_join_tasks_param(self, dynamic_fork_join_tasks_param):
+        """Sets the dynamic_fork_join_tasks_param of this WorkflowTask.
+
+
+        :param dynamic_fork_join_tasks_param: The dynamic_fork_join_tasks_param of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._dynamic_fork_join_tasks_param = dynamic_fork_join_tasks_param
+
+    @property
+    def dynamic_fork_tasks_param(self):
+        """Gets the dynamic_fork_tasks_param of this WorkflowTask.  # noqa: E501
+
+
+        :return: The dynamic_fork_tasks_param of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._dynamic_fork_tasks_param
+
+    @dynamic_fork_tasks_param.setter
+    def dynamic_fork_tasks_param(self, dynamic_fork_tasks_param):
+        """Sets the dynamic_fork_tasks_param of this WorkflowTask.
+
+
+        :param dynamic_fork_tasks_param: The dynamic_fork_tasks_param of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._dynamic_fork_tasks_param = dynamic_fork_tasks_param
+
+    @property
+    def dynamic_fork_tasks_input_param_name(self):
+        """Gets the dynamic_fork_tasks_input_param_name of this WorkflowTask.  # noqa: E501
+
+
+        :return: The dynamic_fork_tasks_input_param_name of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._dynamic_fork_tasks_input_param_name
+
+    @dynamic_fork_tasks_input_param_name.setter
+    def dynamic_fork_tasks_input_param_name(self, dynamic_fork_tasks_input_param_name):
+        """Sets the dynamic_fork_tasks_input_param_name of this WorkflowTask.
+
+
+        :param dynamic_fork_tasks_input_param_name: The dynamic_fork_tasks_input_param_name of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._dynamic_fork_tasks_input_param_name = dynamic_fork_tasks_input_param_name
+
+    @property
+    def default_case(self):
+        """Gets the default_case of this WorkflowTask.  # noqa: E501
+
+
+        :return: The default_case of this WorkflowTask.  # noqa: E501
+        :rtype: list[WorkflowTask]
+        """
+        return self._default_case
+
+    @default_case.setter
+    def default_case(self, default_case):
+        """Sets the default_case of this WorkflowTask.
+
+
+        :param default_case: The default_case of this WorkflowTask.  # noqa: E501
+        :type: list[WorkflowTask]
+        """
+
+        self._default_case = default_case
+
+    @property
+    def fork_tasks(self):
+        """Gets the fork_tasks of this WorkflowTask.  # noqa: E501
+
+
+        :return: The fork_tasks of this WorkflowTask.  # noqa: E501
+        :rtype: list[list[WorkflowTask]]
+        """
+        return self._fork_tasks
+
+    @fork_tasks.setter
+    def fork_tasks(self, fork_tasks):
+        """Sets the fork_tasks of this WorkflowTask.
+
+
+        :param fork_tasks: The fork_tasks of this WorkflowTask.  # noqa: E501
+        :type: list[list[WorkflowTask]]
+        """
+
+        self._fork_tasks = fork_tasks
+
+    @property
+    def start_delay(self):
+        """Gets the start_delay of this WorkflowTask.  # noqa: E501
+
+
+        :return: The start_delay of this WorkflowTask.  # noqa: E501
+        :rtype: int
+        """
+        return self._start_delay
+
+    @start_delay.setter
+    def start_delay(self, start_delay):
+        """Sets the start_delay of this WorkflowTask.
+
+
+        :param start_delay: The start_delay of this WorkflowTask.  # noqa: E501
+        :type: int
+        """
+
+        self._start_delay = start_delay
+
+    @property
+    def sub_workflow_param(self):
+        """Gets the sub_workflow_param of this WorkflowTask.  # noqa: E501
+
+
+        :return: The sub_workflow_param of this WorkflowTask.  # noqa: E501
+        :rtype: SubWorkflowParams
+        """
+        return self._sub_workflow_param
+
+    @sub_workflow_param.setter
+    def sub_workflow_param(self, sub_workflow_param):
+        """Sets the sub_workflow_param of this WorkflowTask.
+
+
+        :param sub_workflow_param: The sub_workflow_param of this WorkflowTask.  # noqa: E501
+        :type: SubWorkflowParams
+        """
+
+        self._sub_workflow_param = sub_workflow_param
+
+    @property
+    def join_on(self):
+        """Gets the join_on of this WorkflowTask.  # noqa: E501
+
+
+        :return: The join_on of this WorkflowTask.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._join_on
+
+    @join_on.setter
+    def join_on(self, join_on):
+        """Sets the join_on of this WorkflowTask.
+
+
+        :param join_on: The join_on of this WorkflowTask.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._join_on = join_on
+
+    @property
+    def sink(self):
+        """Gets the sink of this WorkflowTask.  # noqa: E501
+
+
+        :return: The sink of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._sink
+
+    @sink.setter
+    def sink(self, sink):
+        """Sets the sink of this WorkflowTask.
+
+
+        :param sink: The sink of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._sink = sink
+
+    @property
+    def optional(self):
+        """Gets the optional of this WorkflowTask.  # noqa: E501
+
+
+        :return: The optional of this WorkflowTask.  # noqa: E501
+        :rtype: bool
+        """
+        return self._optional
+
+    @optional.setter
+    def optional(self, optional):
+        """Sets the optional of this WorkflowTask.
+
+
+        :param optional: The optional of this WorkflowTask.  # noqa: E501
+        :type: bool
+        """
+
+        self._optional = optional
+
+    @property
+    def task_definition(self):
+        """Gets the task_definition of this WorkflowTask.  # noqa: E501
+
+
+        :return: The task_definition of this WorkflowTask.  # noqa: E501
+        :rtype: TaskDef
+        """
+        return self._task_definition
+
+    @task_definition.setter
+    def task_definition(self, task_definition):
+        """Sets the task_definition of this WorkflowTask.
+
+
+        :param task_definition: The task_definition of this WorkflowTask.  # noqa: E501
+        :type: TaskDef
+        """
+
+        self._task_definition = task_definition
+
+    @property
+    def rate_limited(self):
+        """Gets the rate_limited of this WorkflowTask.  # noqa: E501
+
+
+        :return: The rate_limited of this WorkflowTask.  # noqa: E501
+        :rtype: bool
+        """
+        return self._rate_limited
+
+    @rate_limited.setter
+    def rate_limited(self, rate_limited):
+        """Sets the rate_limited of this WorkflowTask.
+
+
+        :param rate_limited: The rate_limited of this WorkflowTask.  # noqa: E501
+        :type: bool
+        """
+
+        self._rate_limited = rate_limited
+
+    @property
+    def default_exclusive_join_task(self):
+        """Gets the default_exclusive_join_task of this WorkflowTask.  # noqa: E501
+
+
+        :return: The default_exclusive_join_task of this WorkflowTask.  # noqa: E501
+        :rtype: list[str]
+        """
+        return self._default_exclusive_join_task
+
+    @default_exclusive_join_task.setter
+    def default_exclusive_join_task(self, default_exclusive_join_task):
+        """Sets the default_exclusive_join_task of this WorkflowTask.
+
+
+        :param default_exclusive_join_task: The default_exclusive_join_task of this WorkflowTask.  # noqa: E501
+        :type: list[str]
+        """
+
+        self._default_exclusive_join_task = default_exclusive_join_task
+
+    @property
+    def async_complete(self):
+        """Gets the async_complete of this WorkflowTask.  # noqa: E501
+
+
+        :return: The async_complete of this WorkflowTask.  # noqa: E501
+        :rtype: bool
+        """
+        return self._async_complete
+
+    @async_complete.setter
+    def async_complete(self, async_complete):
+        """Sets the async_complete of this WorkflowTask.
+
+
+        :param async_complete: The async_complete of this WorkflowTask.  # noqa: E501
+        :type: bool
+        """
+
+        self._async_complete = async_complete
+
+    @property
+    def loop_condition(self):
+        """Gets the loop_condition of this WorkflowTask.  # noqa: E501
+
+
+        :return: The loop_condition of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._loop_condition
+
+    @loop_condition.setter
+    def loop_condition(self, loop_condition):
+        """Sets the loop_condition of this WorkflowTask.
+
+
+        :param loop_condition: The loop_condition of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._loop_condition = loop_condition
+
+    @property
+    def loop_over(self):
+        """Gets the loop_over of this WorkflowTask.  # noqa: E501
+
+
+        :return: The loop_over of this WorkflowTask.  # noqa: E501
+        :rtype: list[WorkflowTask]
+        """
+        return self._loop_over
+
+    @loop_over.setter
+    def loop_over(self, loop_over):
+        """Sets the loop_over of this WorkflowTask.
+
+
+        :param loop_over: The loop_over of this WorkflowTask.  # noqa: E501
+        :type: list[WorkflowTask]
+        """
+
+        self._loop_over = loop_over
+
+    @property
+    def retry_count(self):
+        """Gets the retry_count of this WorkflowTask.  # noqa: E501
+
+
+        :return: The retry_count of this WorkflowTask.  # noqa: E501
+        :rtype: int
+        """
+        return self._retry_count
+
+    @retry_count.setter
+    def retry_count(self, retry_count):
+        """Sets the retry_count of this WorkflowTask.
+
+
+        :param retry_count: The retry_count of this WorkflowTask.  # noqa: E501
+        :type: int
+        """
+
+        self._retry_count = retry_count
+
+    @property
+    def evaluator_type(self):
+        """Gets the evaluator_type of this WorkflowTask.  # noqa: E501
+
+
+        :return: The evaluator_type of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._evaluator_type
+
+    @evaluator_type.setter
+    def evaluator_type(self, evaluator_type):
+        """Sets the evaluator_type of this WorkflowTask.
+
+
+        :param evaluator_type: The evaluator_type of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._evaluator_type = evaluator_type
+
+    @property
+    def expression(self):
+        """Gets the expression of this WorkflowTask.  # noqa: E501
+
+
+        :return: The expression of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._expression
+
+    @expression.setter
+    def expression(self, expression):
+        """Sets the expression of this WorkflowTask.
+
+
+        :param expression: The expression of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+
+        self._expression = expression
+
+    @property
+    def workflow_task_type(self):
+        """Gets the workflow_task_type of this WorkflowTask.  # noqa: E501
+
+
+        :return: The workflow_task_type of this WorkflowTask.  # noqa: E501
+        :rtype: str
+        """
+        return self._workflow_task_type
+
+    @workflow_task_type.setter
+    def workflow_task_type(self, workflow_task_type):
+        """Sets the workflow_task_type of this WorkflowTask.
+
+
+        :param workflow_task_type: The workflow_task_type of this WorkflowTask.  # noqa: E501
+        :type: str
+        """
+        self._workflow_task_type = workflow_task_type
+
+    @property
+    def on_state_change(self) -> dict[str, List[StateChangeEvent]]:
+        return self._on_state_change
+
+    @on_state_change.setter
+    def on_state_change(self, state_change: StateChangeConfig):
+        self._on_state_change = {state_change.type: state_change.events}
+
+    @property
+    def cache_config(self) -> CacheConfig:
+        return self._cache_config
+
+    @cache_config.setter
+    def cache_config(self, cache_config: CacheConfig):
+        self._cache_config = cache_config
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowTask, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowTask):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/models/workflow_test_request.py b/omagent_core/engine/http/models/workflow_test_request.py
new file mode 100644
index 0000000000000000000000000000000000000000..bdd5f7b1514dbb824825236a2b1de0e11c0ed3d3
--- /dev/null
+++ b/omagent_core/engine/http/models/workflow_test_request.py
@@ -0,0 +1,384 @@
+# coding: utf-8
+
+import pprint
+import re  # noqa: F401
+
+import six
+
+
+class WorkflowTestRequest(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    """
+
+    """
+    Attributes:
+      swagger_types (dict): The key is attribute name
+                            and the value is attribute type.
+      attribute_map (dict): The key is attribute name
+                            and the value is json key in definition.
+    """
+    swagger_types = {
+        "correlation_id": "str",
+        "created_by": "str",
+        "external_input_payload_storage_path": "str",
+        "input": "dict(str, object)",
+        "name": "str",
+        "priority": "int",
+        "sub_workflow_test_request": "dict(str, WorkflowTestRequest)",
+        "task_ref_to_mock_output": "dict(str, list[TaskMock])",
+        "task_to_domain": "dict(str, str)",
+        "version": "int",
+        "workflow_def": "WorkflowDef",
+    }
+
+    attribute_map = {
+        "correlation_id": "correlationId",
+        "created_by": "createdBy",
+        "external_input_payload_storage_path": "externalInputPayloadStoragePath",
+        "input": "input",
+        "name": "name",
+        "priority": "priority",
+        "sub_workflow_test_request": "subWorkflowTestRequest",
+        "task_ref_to_mock_output": "taskRefToMockOutput",
+        "task_to_domain": "taskToDomain",
+        "version": "version",
+        "workflow_def": "workflowDef",
+    }
+
+    def __init__(
+        self,
+        correlation_id=None,
+        created_by=None,
+        external_input_payload_storage_path=None,
+        input=None,
+        name=None,
+        priority=None,
+        sub_workflow_test_request=None,
+        task_ref_to_mock_output=None,
+        task_to_domain=None,
+        version=None,
+        workflow_def=None,
+    ):  # noqa: E501
+        """WorkflowTestRequest - a model defined in Swagger"""  # noqa: E501
+        self._correlation_id = None
+        self._created_by = None
+        self._external_input_payload_storage_path = None
+        self._input = None
+        self._name = None
+        self._priority = None
+        self._sub_workflow_test_request = None
+        self._task_ref_to_mock_output = None
+        self._task_to_domain = None
+        self._version = None
+        self._workflow_def = None
+        self.discriminator = None
+        if correlation_id is not None:
+            self.correlation_id = correlation_id
+        if created_by is not None:
+            self.created_by = created_by
+        if external_input_payload_storage_path is not None:
+            self.external_input_payload_storage_path = (
+                external_input_payload_storage_path
+            )
+        if input is not None:
+            self.input = input
+        self.name = name
+        if priority is not None:
+            self.priority = priority
+        if sub_workflow_test_request is not None:
+            self.sub_workflow_test_request = sub_workflow_test_request
+        if task_ref_to_mock_output is not None:
+            self.task_ref_to_mock_output = task_ref_to_mock_output
+        if task_to_domain is not None:
+            self.task_to_domain = task_to_domain
+        if version is not None:
+            self.version = version
+        if workflow_def is not None:
+            self.workflow_def = workflow_def
+
+    @property
+    def correlation_id(self):
+        """Gets the correlation_id of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The correlation_id of this WorkflowTestRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._correlation_id
+
+    @correlation_id.setter
+    def correlation_id(self, correlation_id):
+        """Sets the correlation_id of this WorkflowTestRequest.
+
+
+        :param correlation_id: The correlation_id of this WorkflowTestRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._correlation_id = correlation_id
+
+    @property
+    def created_by(self):
+        """Gets the created_by of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The created_by of this WorkflowTestRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._created_by
+
+    @created_by.setter
+    def created_by(self, created_by):
+        """Sets the created_by of this WorkflowTestRequest.
+
+
+        :param created_by: The created_by of this WorkflowTestRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._created_by = created_by
+
+    @property
+    def external_input_payload_storage_path(self):
+        """Gets the external_input_payload_storage_path of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The external_input_payload_storage_path of this WorkflowTestRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._external_input_payload_storage_path
+
+    @external_input_payload_storage_path.setter
+    def external_input_payload_storage_path(self, external_input_payload_storage_path):
+        """Sets the external_input_payload_storage_path of this WorkflowTestRequest.
+
+
+        :param external_input_payload_storage_path: The external_input_payload_storage_path of this WorkflowTestRequest.  # noqa: E501
+        :type: str
+        """
+
+        self._external_input_payload_storage_path = external_input_payload_storage_path
+
+    @property
+    def input(self):
+        """Gets the input of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The input of this WorkflowTestRequest.  # noqa: E501
+        :rtype: dict(str, object)
+        """
+        return self._input
+
+    @input.setter
+    def input(self, input):
+        """Sets the input of this WorkflowTestRequest.
+
+
+        :param input: The input of this WorkflowTestRequest.  # noqa: E501
+        :type: dict(str, object)
+        """
+
+        self._input = input
+
+    @property
+    def name(self):
+        """Gets the name of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The name of this WorkflowTestRequest.  # noqa: E501
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, name):
+        """Sets the name of this WorkflowTestRequest.
+
+
+        :param name: The name of this WorkflowTestRequest.  # noqa: E501
+        :type: str
+        """
+        if name is None:
+            raise ValueError(
+                "Invalid value for `name`, must not be `None`"
+            )  # noqa: E501
+
+        self._name = name
+
+    @property
+    def priority(self):
+        """Gets the priority of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The priority of this WorkflowTestRequest.  # noqa: E501
+        :rtype: int
+        """
+        return self._priority
+
+    @priority.setter
+    def priority(self, priority):
+        """Sets the priority of this WorkflowTestRequest.
+
+
+        :param priority: The priority of this WorkflowTestRequest.  # noqa: E501
+        :type: int
+        """
+
+        self._priority = priority
+
+    @property
+    def sub_workflow_test_request(self):
+        """Gets the sub_workflow_test_request of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The sub_workflow_test_request of this WorkflowTestRequest.  # noqa: E501
+        :rtype: dict(str, WorkflowTestRequest)
+        """
+        return self._sub_workflow_test_request
+
+    @sub_workflow_test_request.setter
+    def sub_workflow_test_request(self, sub_workflow_test_request):
+        """Sets the sub_workflow_test_request of this WorkflowTestRequest.
+
+
+        :param sub_workflow_test_request: The sub_workflow_test_request of this WorkflowTestRequest.  # noqa: E501
+        :type: dict(str, WorkflowTestRequest)
+        """
+
+        self._sub_workflow_test_request = sub_workflow_test_request
+
+    @property
+    def task_ref_to_mock_output(self):
+        """Gets the task_ref_to_mock_output of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The task_ref_to_mock_output of this WorkflowTestRequest.  # noqa: E501
+        :rtype: dict(str, list[TaskMock])
+        """
+        return self._task_ref_to_mock_output
+
+    @task_ref_to_mock_output.setter
+    def task_ref_to_mock_output(self, task_ref_to_mock_output):
+        """Sets the task_ref_to_mock_output of this WorkflowTestRequest.
+
+
+        :param task_ref_to_mock_output: The task_ref_to_mock_output of this WorkflowTestRequest.  # noqa: E501
+        :type: dict(str, list[TaskMock])
+        """
+
+        self._task_ref_to_mock_output = task_ref_to_mock_output
+
+    @property
+    def task_to_domain(self):
+        """Gets the task_to_domain of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The task_to_domain of this WorkflowTestRequest.  # noqa: E501
+        :rtype: dict(str, str)
+        """
+        return self._task_to_domain
+
+    @task_to_domain.setter
+    def task_to_domain(self, task_to_domain):
+        """Sets the task_to_domain of this WorkflowTestRequest.
+
+
+        :param task_to_domain: The task_to_domain of this WorkflowTestRequest.  # noqa: E501
+        :type: dict(str, str)
+        """
+
+        self._task_to_domain = task_to_domain
+
+    @property
+    def version(self):
+        """Gets the version of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The version of this WorkflowTestRequest.  # noqa: E501
+        :rtype: int
+        """
+        return self._version
+
+    @version.setter
+    def version(self, version):
+        """Sets the version of this WorkflowTestRequest.
+
+
+        :param version: The version of this WorkflowTestRequest.  # noqa: E501
+        :type: int
+        """
+
+        self._version = version
+
+    @property
+    def workflow_def(self):
+        """Gets the workflow_def of this WorkflowTestRequest.  # noqa: E501
+
+
+        :return: The workflow_def of this WorkflowTestRequest.  # noqa: E501
+        :rtype: WorkflowDef
+        """
+        return self._workflow_def
+
+    @workflow_def.setter
+    def workflow_def(self, workflow_def):
+        """Sets the workflow_def of this WorkflowTestRequest.
+
+
+        :param workflow_def: The workflow_def of this WorkflowTestRequest.  # noqa: E501
+        :type: WorkflowDef
+        """
+
+        self._workflow_def = workflow_def
+
+    def to_dict(self):
+        """Returns the model properties as a dict"""
+        result = {}
+
+        for attr, _ in six.iteritems(self.swagger_types):
+            value = getattr(self, attr)
+            if isinstance(value, list):
+                result[attr] = list(
+                    map(lambda x: x.to_dict() if hasattr(x, "to_dict") else x, value)
+                )
+            elif hasattr(value, "to_dict"):
+                result[attr] = value.to_dict()
+            elif isinstance(value, dict):
+                result[attr] = dict(
+                    map(
+                        lambda item: (
+                            (item[0], item[1].to_dict())
+                            if hasattr(item[1], "to_dict")
+                            else item
+                        ),
+                        value.items(),
+                    )
+                )
+            else:
+                result[attr] = value
+        if issubclass(WorkflowTestRequest, dict):
+            for key, value in self.items():
+                result[key] = value
+
+        return result
+
+    def to_str(self):
+        """Returns the string representation of the model"""
+        return pprint.pformat(self.to_dict())
+
+    def __repr__(self):
+        """For `print` and `pprint`"""
+        return self.to_str()
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, WorkflowTestRequest):
+            return False
+
+        return self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/http/rest.py b/omagent_core/engine/http/rest.py
new file mode 100644
index 0000000000000000000000000000000000000000..284fffced897ca640d7263b0869cabcbfdd84919
--- /dev/null
+++ b/omagent_core/engine/http/rest.py
@@ -0,0 +1,343 @@
+import io
+import json
+import re
+
+import requests
+from requests.adapters import HTTPAdapter
+from six.moves.urllib.parse import urlencode
+from urllib3 import Retry
+
+
+class RESTResponse(io.IOBase):
+
+    def __init__(self, resp):
+        self.status = resp.status_code
+        self.reason = resp.reason
+        self.resp = resp
+        self.headers = resp.headers
+
+    def getheaders(self):
+        return self.headers
+
+
+class RESTClientObject(object):
+    def __init__(self, connection=None):
+        self.connection = connection or requests.Session()
+        retry_strategy = Retry(
+            total=3,
+            backoff_factor=2,
+            status_forcelist=[429, 500, 502, 503, 504],
+            allowed_methods=[
+                "HEAD",
+                "GET",
+                "OPTIONS",
+                "DELETE",
+            ],  # all the methods that are supposed to be idempotent
+        )
+        self.connection.mount("https://", HTTPAdapter(max_retries=retry_strategy))
+        self.connection.mount("http://", HTTPAdapter(max_retries=retry_strategy))
+
+    def request(
+        self,
+        method,
+        url,
+        query_params=None,
+        headers=None,
+        body=None,
+        post_params=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        """Perform requests.
+
+        :param method: http request method
+        :param url: http request url
+        :param query_params: query parameters in the url
+        :param headers: http request headers
+        :param body: request json body, for `application/json`
+        :param post_params: request post parameters,
+                            `application/x-www-form-urlencoded`
+                            and `multipart/form-data`
+        :param _preload_content: if False, the urllib3.HTTPResponse object will
+                                 be returned without reading/decoding response
+                                 data. Default is True.
+        :param _request_timeout: timeout setting for this request. If one
+                                 number provided, it will be total request
+                                 timeout. It can also be a pair (tuple) of
+                                 (connection, read) timeouts.
+        """
+        method = method.upper()
+        assert method in ["GET", "HEAD", "DELETE", "POST", "PUT", "PATCH", "OPTIONS"]
+
+        if post_params and body:
+            raise ValueError(
+                "body parameter cannot be used with post_params parameter."
+            )
+
+        post_params = post_params or {}
+        headers = headers or {}
+
+        timeout = _request_timeout if _request_timeout is not None else (120, 120)
+
+        if "Content-Type" not in headers:
+            headers["Content-Type"] = "application/json"
+
+        try:
+            # For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
+            if method in ["POST", "PUT", "PATCH", "OPTIONS", "DELETE"]:
+                if query_params:
+                    url += "?" + urlencode(query_params)
+                if re.search(
+                    "json", headers["Content-Type"], re.IGNORECASE
+                ) or isinstance(body, str):
+                    request_body = "{}"
+                    if body is not None:
+                        request_body = json.dumps(body)
+                        if isinstance(body, str):
+                            request_body = request_body.strip('"')
+                    r = self.connection.request(
+                        method, url, data=request_body, timeout=timeout, headers=headers
+                    )
+                else:
+                    # Cannot generate the request from given parameters
+                    msg = """Cannot prepare a request message for provided
+                             arguments. Please check that your arguments match
+                             declared content type."""
+                    raise ApiException(status=0, reason=msg)
+            # For `GET`, `HEAD`
+            else:
+                r = self.connection.request(
+                    method, url, params=query_params, timeout=timeout, headers=headers
+                )
+        except Exception as e:
+            msg = "{0}\n{1}".format(type(e).__name__, str(e))
+            raise ApiException(status=0, reason=msg)
+
+        if _preload_content:
+            r = RESTResponse(r)
+
+        if r.status == 401 or r.status == 403:
+            raise AuthorizationException(http_resp=r)
+
+        if not 200 <= r.status <= 299:
+            raise ApiException(http_resp=r)
+
+        return r
+
+    def GET(
+        self,
+        url,
+        headers=None,
+        query_params=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        return self.request(
+            "GET",
+            url,
+            headers=headers,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+            query_params=query_params,
+        )
+
+    def HEAD(
+        self,
+        url,
+        headers=None,
+        query_params=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        return self.request(
+            "HEAD",
+            url,
+            headers=headers,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+            query_params=query_params,
+        )
+
+    def OPTIONS(
+        self,
+        url,
+        headers=None,
+        query_params=None,
+        post_params=None,
+        body=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        return self.request(
+            "OPTIONS",
+            url,
+            headers=headers,
+            query_params=query_params,
+            post_params=post_params,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+            body=body,
+        )
+
+    def DELETE(
+        self,
+        url,
+        headers=None,
+        query_params=None,
+        body=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        return self.request(
+            "DELETE",
+            url,
+            headers=headers,
+            query_params=query_params,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+            body=body,
+        )
+
+    def POST(
+        self,
+        url,
+        headers=None,
+        query_params=None,
+        post_params=None,
+        body=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        return self.request(
+            "POST",
+            url,
+            headers=headers,
+            query_params=query_params,
+            post_params=post_params,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+            body=body,
+        )
+
+    def PUT(
+        self,
+        url,
+        headers=None,
+        query_params=None,
+        post_params=None,
+        body=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        return self.request(
+            "PUT",
+            url,
+            headers=headers,
+            query_params=query_params,
+            post_params=post_params,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+            body=body,
+        )
+
+    def PATCH(
+        self,
+        url,
+        headers=None,
+        query_params=None,
+        post_params=None,
+        body=None,
+        _preload_content=True,
+        _request_timeout=None,
+    ):
+        return self.request(
+            "PATCH",
+            url,
+            headers=headers,
+            query_params=query_params,
+            post_params=post_params,
+            _preload_content=_preload_content,
+            _request_timeout=_request_timeout,
+            body=body,
+        )
+
+
+class ApiException(Exception):
+
+    def __init__(self, status=None, reason=None, http_resp=None, body=None):
+        if http_resp:
+            self.status = http_resp.status
+            self.code = http_resp.status
+            self.reason = http_resp.reason
+            self.body = http_resp.resp.text
+            try:
+                if http_resp.resp.text:
+                    error = json.loads(http_resp.resp.text)
+                    self.message = error["message"]
+                else:
+                    self.message = http_resp.resp.text
+            except Exception as e:
+                self.message = http_resp.resp.text
+            self.headers = http_resp.getheaders()
+        else:
+            self.status = status
+            self.code = status
+            self.reason = reason
+            self.body = body
+            self.message = body
+            self.headers = None
+
+    def __str__(self):
+        """Custom error messages for exception"""
+        error_message = "({0})\n" "Reason: {1}\n".format(self.status, self.reason)
+        if self.headers:
+            error_message += "HTTP response headers: {0}\n".format(self.headers)
+
+        if self.body:
+            error_message += "HTTP response body: {0}\n".format(self.body)
+
+        return error_message
+
+    def is_not_found(self) -> bool:
+        return self.code == 404
+
+
+class AuthorizationException(ApiException):
+    def __init__(self, status=None, reason=None, http_resp=None, body=None):
+        try:
+            data = json.loads(http_resp.resp.text)
+            if "error" in data:
+                self._error_code = data["error"]
+            else:
+                self._error_code = ""
+        except Exception:
+            self._error_code = ""
+        super().__init__(status, reason, http_resp, body)
+
+    @property
+    def error_code(self):
+        return self._error_code
+
+    @property
+    def status_code(self):
+        return self.status
+
+    @property
+    def token_expired(self) -> bool:
+        return self._error_code == "EXPIRED_TOKEN"
+
+    @property
+    def invalid_token(self) -> bool:
+        return self._error_code == "INVALID_TOKEN"
+
+    def __str__(self):
+        """Custom error messages for exception"""
+        error_message = f"authorization error: {self._error_code}.  status_code: {self.status}, reason: {self.reason}"
+
+        if self.headers:
+            error_message += f", headers: {self.headers}"
+
+        if self.body:
+            error_message += f", response: {self.body}"
+
+        return error_message
diff --git a/omagent_core/engine/http/thread.py b/omagent_core/engine/http/thread.py
new file mode 100644
index 0000000000000000000000000000000000000000..78c52c1f3f5159cd049ce941442b7a92efe9af12
--- /dev/null
+++ b/omagent_core/engine/http/thread.py
@@ -0,0 +1,16 @@
+import threading
+
+
+class AwaitableThread(threading.Thread):
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        self._result = None
+
+    def run(self):
+        self._result = self._target(*self._args, **self._kwargs)
+
+    def wait(self):
+        self.join()
+
+    def get(self):
+        return self._result
diff --git a/omagent_core/engine/integration_client.py b/omagent_core/engine/integration_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..73e6e07ccef2113ba95a07d20d5e875235cdedce
--- /dev/null
+++ b/omagent_core/engine/integration_client.py
@@ -0,0 +1,112 @@
+from __future__ import absolute_import
+
+from abc import ABC, abstractmethod
+from typing import List
+
+from omagent_core.engine.http.models.integration import Integration
+from omagent_core.engine.http.models.integration_api import IntegrationApi
+from omagent_core.engine.http.models.integration_api_update import \
+    IntegrationApiUpdate
+from omagent_core.engine.http.models.integration_update import \
+    IntegrationUpdate
+from omagent_core.engine.http.models.prompt_template import PromptTemplate
+
+
+class IntegrationClient(ABC):
+    """Client for managing integrations with external systems.  Some examples of integrations are:
+    1. AI/LLM providers (e.g. OpenAI, HuggingFace)
+    2. Vector DBs (Pinecone, Weaviate etc.)
+    3. Kafka
+    4. Relational databases
+
+    Integrations are configured as integration -> api with 1->N cardinality.
+    APIs are the underlying resources for an integration and depending on the type of integration they represent underlying resources.
+    Examples:
+        LLM integrations
+        The integration specifies the name of the integration unique to your environment, api keys and endpoint used.
+        APIs are the models (e.g. text-davinci-003, or text-embedding-ada-002)
+
+        Vector DB integrations,
+        The integration represents the cluster, specifies the name of the integration unique to your environment, api keys and endpoint used.
+        APIs are the indexes (e.g. pinecone) or class (e.g. for weaviate)
+
+        Kafka
+        The integration represents the cluster, specifies the name of the integration unique to your environment, api keys and endpoint used.
+        APIs are the topics that are configured for use within this kafka cluster
+    """
+
+    @abstractmethod
+    def associate_prompt_with_integration(
+        self, ai_integration: str, model_name: str, prompt_name: str
+    ):
+        """Associate a prompt with an AI integration and model"""
+        pass
+
+    @abstractmethod
+    def delete_integration_api(self, api_name: str, integration_name: str):
+        """Delete a specific integration api for a given integration"""
+        pass
+
+    def delete_integration(self, integration_name: str):
+        """Delete an integration"""
+        pass
+
+    def get_integration_api(
+        self, api_name: str, integration_name: str
+    ) -> IntegrationApi:
+        pass
+
+    def get_integration_apis(self, integration_name: str) -> List[IntegrationApi]:
+        pass
+
+    def get_integration(self, integration_name: str) -> Integration:
+        pass
+
+    def get_integrations(self) -> List[Integration]:
+        """Returns the list of all the available integrations"""
+        pass
+
+    def get_prompts_with_integration(
+        self, ai_integration: str, model_name: str
+    ) -> List[PromptTemplate]:
+        pass
+
+    def get_token_usage_for_integration(self, name, integration_name) -> int:
+        pass
+
+    def get_token_usage_for_integration_provider(self, name) -> dict:
+        pass
+
+    def register_token_usage(self, body, name, integration_name):
+        pass
+
+    def save_integration_api(
+        self, integration_name, api_name, api_details: IntegrationApiUpdate
+    ):
+        pass
+
+    def save_integration(
+        self, integration_name, integration_details: IntegrationUpdate
+    ):
+        pass
+
+    # Tags
+
+    def delete_tag_for_integration(self, body, tag_name, integration_name):
+        """Delete an integration"""
+        pass
+
+    def delete_tag_for_integration_provider(self, body, name):
+        pass
+
+    def put_tag_for_integration(self, body, name, integration_name):
+        pass
+
+    def put_tag_for_integration_provider(self, body, name):
+        pass
+
+    def get_tags_for_integration(self, name, integration_name):
+        pass
+
+    def get_tags_for_integration_provider(self, name):
+        pass
diff --git a/omagent_core/engine/metadata_client.py b/omagent_core/engine/metadata_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..d493a7286c1ef4563ad864333b199083c7c58ce9
--- /dev/null
+++ b/omagent_core/engine/metadata_client.py
@@ -0,0 +1,63 @@
+from abc import ABC, abstractmethod
+from typing import List, Optional
+
+from omagent_core.engine.http.models.task_def import TaskDef
+from omagent_core.engine.http.models.workflow_def import WorkflowDef
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+
+
+class MetadataClient(ABC):
+    @abstractmethod
+    def register_workflow_def(
+        self, workflow_def: WorkflowDef, overwrite: Optional[bool]
+    ):
+        pass
+
+    @abstractmethod
+    def update_workflow_def(self, workflow_def: WorkflowDef, overwrite: Optional[bool]):
+        pass
+
+    @abstractmethod
+    def unregister_workflow_def(self, workflow_name: str, version: int):
+        pass
+
+    @abstractmethod
+    def get_workflow_def(self, name: str, version: Optional[int]) -> WorkflowDef:
+        pass
+
+    @abstractmethod
+    def get_all_workflow_defs(self) -> List[WorkflowDef]:
+        pass
+
+    @abstractmethod
+    def register_task_def(self, task_def: TaskDef):
+        pass
+
+    @abstractmethod
+    def update_task_def(self, task_def: TaskDef):
+        pass
+
+    @abstractmethod
+    def unregister_task_def(self, task_type: str):
+        pass
+
+    @abstractmethod
+    def get_task_def(self, task_type: str) -> TaskDef:
+        pass
+
+    @abstractmethod
+    def get_all_task_defs(self) -> List[TaskDef]:
+        pass
+
+    @abstractmethod
+    def add_workflow_tag(self, tag: MetadataTag, workflow_name: str):
+        pass
+
+    def get_workflow_tags(self, workflow_name: str) -> List[MetadataTag]:
+        pass
+
+    def set_workflow_tags(self, tags: List[MetadataTag], workflow_name: str):
+        pass
+
+    def delete_workflow_tag(self, tag: MetadataTag, workflow_name: str):
+        pass
diff --git a/omagent_core/engine/orkes/__init__.py b/omagent_core/engine/orkes/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/orkes/api/__init__.py b/omagent_core/engine/orkes/api/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/orkes/api/tags_api.py b/omagent_core/engine/orkes/api/tags_api.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a323bc288ec3f78719ab79f3c1a762cb471b8d6
--- /dev/null
+++ b/omagent_core/engine/orkes/api/tags_api.py
@@ -0,0 +1,1018 @@
+# coding: utf-8
+
+"""
+    Orkes Conductor API Server
+
+    Orkes Conductor API Server  # noqa: E501
+
+    OpenAPI spec version: v2
+    
+    Generated by: https://github.com/swagger-api/swagger-codegen.git
+"""
+
+from __future__ import absolute_import
+
+import re  # noqa: F401
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+
+
+class TagsApi(object):
+    """NOTE: This class is auto generated by the swagger code generator program.
+
+    Do not edit the class manually.
+    Ref: https://github.com/swagger-api/swagger-codegen
+    """
+
+    def __init__(self, api_client=None):
+        if api_client is None:
+            api_client = ApiClient()
+        self.api_client = api_client
+
+    def add_task_tag(self, body, task_name, **kwargs):  # noqa: E501
+        """Adds the tag to the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_task_tag(body, task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagObject body: (required)
+        :param str task_name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.add_task_tag_with_http_info(
+                body, task_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.add_task_tag_with_http_info(
+                body, task_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def add_task_tag_with_http_info(self, body, task_name, **kwargs):  # noqa: E501
+        """Adds the tag to the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_task_tag_with_http_info(body, task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagObject body: (required)
+        :param str task_name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "task_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method add_task_tag" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `add_task_tag`"
+            )  # noqa: E501
+        # verify the required parameter 'task_name' is set
+        if "task_name" not in params or params["task_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_name` when calling `add_task_tag`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_name" in params:
+            path_params["taskName"] = params["task_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/task/{taskName}/tags",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def add_workflow_tag(self, body, name, **kwargs):  # noqa: E501
+        """Adds the tag to the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_workflow_tag(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagObject body: (required)
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.add_workflow_tag_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.add_workflow_tag_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def add_workflow_tag_with_http_info(self, body, name, **kwargs):  # noqa: E501
+        """Adds the tag to the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.add_workflow_tag_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagObject body: (required)
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method add_workflow_tag" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `add_workflow_tag`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `add_workflow_tag`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow/{name}/tags",
+            "POST",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_task_tag(self, body, task_name, **kwargs):  # noqa: E501
+        """Removes the tag of the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_task_tag(body, task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagString body: (required)
+        :param str task_name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_task_tag_with_http_info(
+                body, task_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_task_tag_with_http_info(
+                body, task_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_task_tag_with_http_info(self, body, task_name, **kwargs):  # noqa: E501
+        """Removes the tag of the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_task_tag_with_http_info(body, task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagString body: (required)
+        :param str task_name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "task_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_task_tag" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_task_tag`"
+            )  # noqa: E501
+        # verify the required parameter 'task_name' is set
+        if "task_name" not in params or params["task_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_name` when calling `delete_task_tag`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_name" in params:
+            path_params["taskName"] = params["task_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/task/{taskName}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def delete_workflow_tag(self, body, name, **kwargs):  # noqa: E501
+        """Removes the tag of the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_workflow_tag(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagObject body: (required)
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.delete_workflow_tag_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.delete_workflow_tag_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def delete_workflow_tag_with_http_info(self, body, name, **kwargs):  # noqa: E501
+        """Removes the tag of the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.delete_workflow_tag_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param TagObject body: (required)
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method delete_workflow_tag" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `delete_workflow_tag`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `delete_workflow_tag`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow/{name}/tags",
+            "DELETE",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_tags1(self, **kwargs):  # noqa: E501
+        """List all tags  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags1(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_tags1_with_http_info(**kwargs)  # noqa: E501
+        else:
+            (data) = self.get_tags1_with_http_info(**kwargs)  # noqa: E501
+            return data
+
+    def get_tags1_with_http_info(self, **kwargs):  # noqa: E501
+        """List all tags  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_tags1_with_http_info(async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = []  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_tags1" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+
+        collection_formats = {}
+
+        path_params = {}
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_task_tags(self, task_name, **kwargs):  # noqa: E501
+        """Returns all the tags of the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_tags(task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_task_tags_with_http_info(task_name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_task_tags_with_http_info(
+                task_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def get_task_tags_with_http_info(self, task_name, **kwargs):  # noqa: E501
+        """Returns all the tags of the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_task_tags_with_http_info(task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str task_name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["task_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_task_tags" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'task_name' is set
+        if "task_name" not in params or params["task_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_name` when calling `get_task_tags`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_name" in params:
+            path_params["taskName"] = params["task_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/task/{taskName}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def get_workflow_tags(self, name, **kwargs):  # noqa: E501
+        """Returns all the tags of the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflow_tags(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.get_workflow_tags_with_http_info(name, **kwargs)  # noqa: E501
+        else:
+            (data) = self.get_workflow_tags_with_http_info(name, **kwargs)  # noqa: E501
+            return data
+
+    def get_workflow_tags_with_http_info(self, name, **kwargs):  # noqa: E501
+        """Returns all the tags of the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.get_workflow_tags_with_http_info(name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param str name: (required)
+        :return: list[TagObject]
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method get_workflow_tags" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `get_workflow_tags`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["application/json"]
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow/{name}/tags",
+            "GET",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="list[TagObject]",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def set_task_tags(self, body, task_name, **kwargs):  # noqa: E501
+        """Adds the tag to the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.set_task_tags(body, task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str task_name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.set_task_tags_with_http_info(
+                body, task_name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.set_task_tags_with_http_info(
+                body, task_name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def set_task_tags_with_http_info(self, body, task_name, **kwargs):  # noqa: E501
+        """Adds the tag to the task  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.set_task_tags_with_http_info(body, task_name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str task_name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "task_name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method set_task_tags" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `set_task_tags`"
+            )  # noqa: E501
+        # verify the required parameter 'task_name' is set
+        if "task_name" not in params or params["task_name"] is None:
+            raise ValueError(
+                "Missing the required parameter `task_name` when calling `set_task_tags`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "task_name" in params:
+            path_params["taskName"] = params["task_name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/task/{taskName}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
+
+    def set_workflow_tags(self, body, name, **kwargs):  # noqa: E501
+        """Set the tags of the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.set_workflow_tags(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+        kwargs["_return_http_data_only"] = True
+        if kwargs.get("async_req"):
+            return self.set_workflow_tags_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+        else:
+            (data) = self.set_workflow_tags_with_http_info(
+                body, name, **kwargs
+            )  # noqa: E501
+            return data
+
+    def set_workflow_tags_with_http_info(self, body, name, **kwargs):  # noqa: E501
+        """Set the tags of the workflow  # noqa: E501
+
+        This method makes a synchronous HTTP request by default. To make an
+        asynchronous HTTP request, please pass async_req=True
+        >>> thread = api.set_workflow_tags_with_http_info(body, name, async_req=True)
+        >>> result = thread.get()
+
+        :param async_req bool
+        :param list[TagObject] body: (required)
+        :param str name: (required)
+        :return: object
+                 If the method is called asynchronously,
+                 returns the request thread.
+        """
+
+        all_params = ["body", "name"]  # noqa: E501
+        all_params.append("async_req")
+        all_params.append("_return_http_data_only")
+        all_params.append("_preload_content")
+        all_params.append("_request_timeout")
+
+        params = locals()
+        for key, val in six.iteritems(params["kwargs"]):
+            if key not in all_params:
+                raise TypeError(
+                    "Got an unexpected keyword argument '%s'"
+                    " to method set_workflow_tags" % key
+                )
+            params[key] = val
+        del params["kwargs"]
+        # verify the required parameter 'body' is set
+        if "body" not in params or params["body"] is None:
+            raise ValueError(
+                "Missing the required parameter `body` when calling `set_workflow_tags`"
+            )  # noqa: E501
+        # verify the required parameter 'name' is set
+        if "name" not in params or params["name"] is None:
+            raise ValueError(
+                "Missing the required parameter `name` when calling `set_workflow_tags`"
+            )  # noqa: E501
+
+        collection_formats = {}
+
+        path_params = {}
+        if "name" in params:
+            path_params["name"] = params["name"]  # noqa: E501
+
+        query_params = []
+
+        header_params = {}
+
+        form_params = []
+        local_var_files = {}
+
+        body_params = None
+        if "body" in params:
+            body_params = params["body"]
+        # HTTP header `Accept`
+        header_params["Accept"] = self.api_client.select_header_accept(
+            ["*/*"]
+        )  # noqa: E501
+
+        # HTTP header `Content-Type`
+        header_params["Content-Type"] = (
+            self.api_client.select_header_content_type(  # noqa: E501
+                ["application/json"]
+            )
+        )  # noqa: E501
+
+        # Authentication setting
+        auth_settings = ["api_key"]  # noqa: E501
+
+        return self.api_client.call_api(
+            "/metadata/workflow/{name}/tags",
+            "PUT",
+            path_params,
+            query_params,
+            header_params,
+            body=body_params,
+            post_params=form_params,
+            files=local_var_files,
+            response_type="object",  # noqa: E501
+            auth_settings=auth_settings,
+            async_req=params.get("async_req"),
+            _return_http_data_only=params.get("_return_http_data_only"),
+            _preload_content=params.get("_preload_content", True),
+            _request_timeout=params.get("_request_timeout"),
+            collection_formats=collection_formats,
+        )
diff --git a/omagent_core/engine/orkes/models/__init__.py b/omagent_core/engine/orkes/models/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/orkes/models/access_key.py b/omagent_core/engine/orkes/models/access_key.py
new file mode 100644
index 0000000000000000000000000000000000000000..688fd264a7d90a59ab39f17d46a069dc7689afc7
--- /dev/null
+++ b/omagent_core/engine/orkes/models/access_key.py
@@ -0,0 +1,68 @@
+from omagent_core.engine.orkes.models.access_key_status import AccessKeyStatus
+from typing_extensions import Self
+
+
+class AccessKey:
+    def __init__(self, id: str, status: AccessKeyStatus, created_at: int) -> Self:
+        self._id = id
+        self._status = status
+        self._created_at = created_at
+
+        if self._status is None:
+            self._status = AccessKeyStatus.ACTIVE
+
+    @property
+    def id(self):
+        """Gets the id of this CreatedAccessKey.  # noqa: E501
+
+        :return: The id of this CreatedAccessKey.  # noqa: E501
+        :rtype: idRef
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this CreatedAccessKey.
+
+        :param id: The id of this CreatedAccessKey.  # noqa: E501
+        :type: str
+        """
+        self._id = id
+
+    @property
+    def status(self):
+        """Gets the status of this CreatedAccessKey.  # noqa: E501
+
+        :return: The status of this CreatedAccessKey.  # noqa: E501
+        :rtype: str
+        """
+        return self._status
+
+    @status.setter
+    def status(self, status):
+        """Sets the status of this CreatedAccessKey.
+
+        :param id: The status of this CreatedAccessKey.  # noqa: E501
+        :type: str
+        """
+        self._status = status
+
+    @property
+    def created_at(self):
+        """Gets the created_at of this CreatedAccessKey.  # noqa: E501
+
+        :return: The created_at of this CreatedAccessKey.  # noqa: E501
+        :rtype: int
+        """
+        return self._created_at
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, AccessKey):
+            return False
+
+        return self.id == other.id and self.status == other.status
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/orkes/models/access_key_status.py b/omagent_core/engine/orkes/models/access_key_status.py
new file mode 100644
index 0000000000000000000000000000000000000000..c9302f83499a12a129c24487161063ec93057099
--- /dev/null
+++ b/omagent_core/engine/orkes/models/access_key_status.py
@@ -0,0 +1,6 @@
+from enum import Enum
+
+
+class AccessKeyStatus(str, Enum):
+    ACTIVE = ("ACTIVE",)
+    INACTIVE = "INACTIVE"
diff --git a/omagent_core/engine/orkes/models/access_type.py b/omagent_core/engine/orkes/models/access_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ae0d1306d55a6286708f3ecb4f8693553914350
--- /dev/null
+++ b/omagent_core/engine/orkes/models/access_type.py
@@ -0,0 +1,9 @@
+from enum import Enum
+
+
+class AccessType(str, Enum):
+    CREATE = ("CREATE",)
+    READ = ("READ",)
+    UPDATE = ("UPDATE",)
+    DELETE = ("DELETE",)
+    EXECUTE = "EXECUTE"
diff --git a/omagent_core/engine/orkes/models/created_access_key.py b/omagent_core/engine/orkes/models/created_access_key.py
new file mode 100644
index 0000000000000000000000000000000000000000..c9b5e55449582c128f224afa02173d887eca41f6
--- /dev/null
+++ b/omagent_core/engine/orkes/models/created_access_key.py
@@ -0,0 +1,54 @@
+from typing_extensions import Self
+
+
+class CreatedAccessKey:
+    def __init__(self, id: str, secret: str) -> Self:
+        self._id = id
+        self._secret = secret
+
+    @property
+    def id(self):
+        """Gets the id of this CreatedAccessKey.  # noqa: E501
+
+        :return: The id of this CreatedAccessKey.  # noqa: E501
+        :rtype: idRef
+        """
+        return self._id
+
+    @id.setter
+    def id(self, id):
+        """Sets the id of this CreatedAccessKey.
+
+        :param id: The id of this CreatedAccessKey.  # noqa: E501
+        :type: str
+        """
+        self._id = id
+
+    @property
+    def secret(self):
+        """Gets the secret of this CreatedAccessKey.  # noqa: E501
+
+        :return: The secret of this CreatedAccessKey.  # noqa: E501
+        :rtype: str
+        """
+        return self._secret
+
+    @secret.setter
+    def secret(self, secret):
+        """Sets the secret of this CreatedAccessKey.
+
+        :param id: The secret of this CreatedAccessKey.  # noqa: E501
+        :type: str
+        """
+        self._secret = secret
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, CreatedAccessKey):
+            return False
+
+        return self.id == other.id and self.secret == other.secret
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/orkes/models/granted_permission.py b/omagent_core/engine/orkes/models/granted_permission.py
new file mode 100644
index 0000000000000000000000000000000000000000..c482956a92e81aacd7276d7d9fa98f8850a44325
--- /dev/null
+++ b/omagent_core/engine/orkes/models/granted_permission.py
@@ -0,0 +1,61 @@
+from typing import List
+
+from omagent_core.engine.http.models.target_ref import TargetRef
+from typing_extensions import Self
+
+
+class GrantedPermission:
+    def __init__(self, target: TargetRef, access: List[str]) -> Self:
+        self._target = target
+        self._access = access
+
+    @property
+    def target(self):
+        """Gets the target of this GrantedPermission.  # noqa: E501
+
+
+        :return: The target of this GrantedPermission.  # noqa: E501
+        :rtype: TargetRef
+        """
+        return self._target
+
+    @target.setter
+    def target(self, target):
+        """Sets the target of this GrantedPermission.
+
+
+        :param target: The target of this GrantedPermission.  # noqa: E501
+        :type: TargetRef
+        """
+        self._target = target
+
+    @property
+    def access(self):
+        """Gets the access of this GrantedPermission.  # noqa: E501
+
+
+        :return: The access of this GrantedPermission.  # noqa: E501
+        :rtype: List[str]
+        """
+        return self._access
+
+    @access.setter
+    def access(self, access):
+        """Sets the access of this GrantedPermission.
+
+
+        :param target: The access of this GrantedPermission.  # noqa: E501
+        :type: List[str]
+        """
+        self._access = access
+
+    def __eq__(self, other):
+        """Returns true if both objects are equal"""
+        if not isinstance(other, GrantedPermission):
+            return False
+
+        return self.target == other.target and self.access == other.access
+
+    def __ne__(self, other):
+        """Returns true if both objects are not equal"""
+        return not self == other
diff --git a/omagent_core/engine/orkes/models/metadata_tag.py b/omagent_core/engine/orkes/models/metadata_tag.py
new file mode 100644
index 0000000000000000000000000000000000000000..5c9dcf9a85fd1c903180056cfce49b5b6e22eec8
--- /dev/null
+++ b/omagent_core/engine/orkes/models/metadata_tag.py
@@ -0,0 +1,7 @@
+from omagent_core.engine.http.models.tag_object import TagObject
+from typing_extensions import Self
+
+
+class MetadataTag(TagObject):
+    def __init__(self, key: str, value: str) -> Self:
+        super().__init__(key=key, type="METADATA", value=value)
diff --git a/omagent_core/engine/orkes/models/ratelimit_tag.py b/omagent_core/engine/orkes/models/ratelimit_tag.py
new file mode 100644
index 0000000000000000000000000000000000000000..12eb42523894f9d30884fb8ebd0ae54e777b3cb7
--- /dev/null
+++ b/omagent_core/engine/orkes/models/ratelimit_tag.py
@@ -0,0 +1,7 @@
+from omagent_core.engine.http.models.tag_object import TagObject
+from typing_extensions import Self
+
+
+class RateLimitTag(TagObject):
+    def __init__(self, key: str, value: int) -> Self:
+        super().__init__(key=key, type="RATE_LIMIT", value=value)
diff --git a/omagent_core/engine/orkes/orkes_authorization_client.py b/omagent_core/engine/orkes/orkes_authorization_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..4192e0951f1904cb429ef41c1244ca1bdef053f3
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_authorization_client.py
@@ -0,0 +1,201 @@
+from typing import Dict, List, Optional
+
+from omagent_core.engine.authorization_client import AuthorizationClient
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models.authorization_request import \
+    AuthorizationRequest
+from omagent_core.engine.http.models.conductor_application import \
+    ConductorApplication
+from omagent_core.engine.http.models.conductor_user import ConductorUser
+from omagent_core.engine.http.models.create_or_update_application_request import \
+    CreateOrUpdateApplicationRequest
+from omagent_core.engine.http.models.group import Group
+from omagent_core.engine.http.models.subject_ref import SubjectRef
+from omagent_core.engine.http.models.target_ref import TargetRef
+from omagent_core.engine.http.models.upsert_group_request import \
+    UpsertGroupRequest
+from omagent_core.engine.http.models.upsert_user_request import \
+    UpsertUserRequest
+from omagent_core.engine.orkes.models.access_key import AccessKey
+from omagent_core.engine.orkes.models.access_type import AccessType
+from omagent_core.engine.orkes.models.created_access_key import \
+    CreatedAccessKey
+from omagent_core.engine.orkes.models.granted_permission import \
+    GrantedPermission
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+
+
+class OrkesAuthorizationClient(OrkesBaseClient, AuthorizationClient):
+    def __init__(self, configuration: Configuration):
+        super(OrkesAuthorizationClient, self).__init__(configuration)
+
+    # Applications
+    def create_application(
+        self, create_or_update_application_request: CreateOrUpdateApplicationRequest
+    ) -> ConductorApplication:
+        app_obj = self.applicationResourceApi.create_application(
+            create_or_update_application_request
+        )
+        return self.api_client.deserialize_class(app_obj, "ConductorApplication")
+
+    def get_application(self, application_id: str) -> ConductorApplication:
+        app_obj = self.applicationResourceApi.get_application(application_id)
+        return self.api_client.deserialize_class(app_obj, "ConductorApplication")
+
+    def list_applications(self) -> List[ConductorApplication]:
+        return self.applicationResourceApi.list_applications()
+
+    def update_application(
+        self,
+        create_or_update_application_request: CreateOrUpdateApplicationRequest,
+        application_id: str,
+    ) -> ConductorApplication:
+        app_obj = self.applicationResourceApi.update_application(
+            create_or_update_application_request, application_id
+        )
+        return self.api_client.deserialize_class(app_obj, "ConductorApplication")
+
+    def delete_application(self, application_id: str):
+        self.applicationResourceApi.delete_application(application_id)
+
+    def add_role_to_application_user(self, application_id: str, role: str):
+        self.applicationResourceApi.add_role_to_application_user(application_id, role)
+
+    def remove_role_from_application_user(self, application_id: str, role: str):
+        self.applicationResourceApi.remove_role_from_application_user(
+            application_id, role
+        )
+
+    def set_application_tags(self, tags: List[MetadataTag], application_id: str):
+        self.applicationResourceApi.put_tags_for_application(tags, application_id)
+
+    def get_application_tags(self, application_id: str) -> List[MetadataTag]:
+        return self.applicationResourceApi.get_tags_for_application(application_id)
+
+    def delete_application_tags(self, tags: List[MetadataTag], application_id: str):
+        self.applicationResourceApi.delete_tags_for_application(tags, application_id)
+
+    def create_access_key(self, application_id: str) -> CreatedAccessKey:
+        key_obj = self.applicationResourceApi.create_access_key(application_id)
+        created_access_key = CreatedAccessKey(key_obj["id"], key_obj["secret"])
+        return created_access_key
+
+    def get_access_keys(self, application_id: str) -> List[AccessKey]:
+        access_keys_obj = self.applicationResourceApi.get_access_keys(application_id)
+
+        access_keys = []
+        for key_obj in access_keys_obj:
+            access_key = AccessKey(
+                key_obj["id"], key_obj["status"], key_obj["createdAt"]
+            )
+            access_keys.append(access_key)
+
+        return access_keys
+
+    def toggle_access_key_status(self, application_id: str, key_id: str) -> AccessKey:
+        key_obj = self.applicationResourceApi.toggle_access_key_status(
+            application_id, key_id
+        )
+        return AccessKey(key_obj["id"], key_obj["status"], key_obj["createdAt"])
+
+    def delete_access_key(self, application_id: str, key_id: str):
+        self.applicationResourceApi.delete_access_key(application_id, key_id)
+
+    # Users
+
+    def upsert_user(
+        self, upsert_user_request: UpsertUserRequest, user_id: str
+    ) -> ConductorUser:
+        user_obj = self.userResourceApi.upsert_user(upsert_user_request, user_id)
+        return self.api_client.deserialize_class(user_obj, "ConductorUser")
+
+    def get_user(self, user_id: str) -> ConductorUser:
+        user_obj = self.userResourceApi.get_user(user_id)
+        return self.api_client.deserialize_class(user_obj, "ConductorUser")
+
+    def list_users(self, apps: Optional[bool] = False) -> List[ConductorUser]:
+        kwargs = {"apps": apps}
+        return self.userResourceApi.list_users(**kwargs)
+
+    def delete_user(self, user_id: str):
+        self.userResourceApi.delete_user(user_id)
+
+    # Groups
+
+    def upsert_group(
+        self, upsert_group_request: UpsertGroupRequest, group_id: str
+    ) -> Group:
+        group_obj = self.groupResourceApi.upsert_group(upsert_group_request, group_id)
+        return self.api_client.deserialize_class(group_obj, "Group")
+
+    def get_group(self, group_id: str) -> Group:
+        group_obj = self.groupResourceApi.get_group(group_id)
+        return self.api_client.deserialize_class(group_obj, "Group")
+
+    def list_groups(self) -> List[Group]:
+        return self.groupResourceApi.list_groups()
+
+    def delete_group(self, group_id: str):
+        self.groupResourceApi.delete_group(group_id)
+
+    def add_user_to_group(self, group_id: str, user_id: str):
+        self.groupResourceApi.add_user_to_group(group_id, user_id)
+
+    def get_users_in_group(self, group_id: str) -> List[ConductorUser]:
+        user_objs = self.groupResourceApi.get_users_in_group(group_id)
+        group_users = []
+        for u in user_objs:
+            c_user = self.api_client.deserialize_class(u, "ConductorUser")
+            group_users.append(c_user)
+
+        return group_users
+
+    def remove_user_from_group(self, group_id: str, user_id: str):
+        self.groupResourceApi.remove_user_from_group(group_id, user_id)
+
+    # Permissions
+
+    def grant_permissions(
+        self, subject: SubjectRef, target: TargetRef, access: List[AccessType]
+    ):
+        req = AuthorizationRequest(subject, target, access)
+        self.authorizationResourceApi.grant_permissions(req)
+
+    def get_permissions(self, target: TargetRef) -> Dict[str, List[SubjectRef]]:
+        resp_obj = self.authorizationResourceApi.get_permissions(
+            target.type.name, target.id
+        )
+        permissions = {}
+        for access_type, subjects in resp_obj.items():
+            subject_list = []
+            for sub in subjects:
+                subject_list.append(SubjectRef(sub["type"], sub["id"]))
+            permissions[access_type] = subject_list
+        return permissions
+
+    def get_granted_permissions_for_group(
+        self, group_id: str
+    ) -> List[GrantedPermission]:
+        granted_access_obj = self.groupResourceApi.get_granted_permissions1(group_id)
+        granted_permissions = []
+        for ga in granted_access_obj["grantedAccess"]:
+            target = TargetRef(ga["target"]["type"], ga["target"]["id"])
+            access = ga["access"]
+            granted_permissions.append(GrantedPermission(target, access))
+        return granted_permissions
+
+    def get_granted_permissions_for_user(self, user_id: str) -> List[GrantedPermission]:
+        granted_access_obj = self.userResourceApi.get_granted_permissions(user_id)
+        granted_permissions = []
+        for ga in granted_access_obj["grantedAccess"]:
+            target = TargetRef(ga["target"]["type"], ga["target"]["id"])
+            access = ga["access"]
+            granted_permissions.append(GrantedPermission(target, access))
+        return granted_permissions
+
+    def remove_permissions(
+        self, subject: SubjectRef, target: TargetRef, access: List[AccessType]
+    ):
+        req = AuthorizationRequest(subject, target, access)
+        self.authorizationResourceApi.remove_permissions(req)
diff --git a/omagent_core/engine/orkes/orkes_base_client.py b/omagent_core/engine/orkes/orkes_base_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..c06daaca192c1018d214043a9274f7c3af30c5ba
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_base_client.py
@@ -0,0 +1,42 @@
+import logging
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.api.application_resource_api import \
+    ApplicationResourceApi
+from omagent_core.engine.http.api.authorization_resource_api import \
+    AuthorizationResourceApi
+from omagent_core.engine.http.api.group_resource_api import GroupResourceApi
+from omagent_core.engine.http.api.integration_resource_api import \
+    IntegrationResourceApi
+from omagent_core.engine.http.api.metadata_resource_api import \
+    MetadataResourceApi
+from omagent_core.engine.http.api.prompt_resource_api import PromptResourceApi
+from omagent_core.engine.http.api.scheduler_resource_api import \
+    SchedulerResourceApi
+from omagent_core.engine.http.api.secret_resource_api import SecretResourceApi
+from omagent_core.engine.http.api.task_resource_api import TaskResourceApi
+from omagent_core.engine.http.api.user_resource_api import UserResourceApi
+from omagent_core.engine.http.api.workflow_resource_api import \
+    WorkflowResourceApi
+from omagent_core.engine.http.api_client import ApiClient
+from omagent_core.engine.orkes.api.tags_api import TagsApi
+
+
+class OrkesBaseClient(object):
+    def __init__(self, configuration: Configuration):
+        self.api_client = ApiClient(configuration)
+        self.logger = logging.getLogger(
+            Configuration.get_logging_formatted_name(__name__)
+        )
+        self.metadataResourceApi = MetadataResourceApi(self.api_client)
+        self.taskResourceApi = TaskResourceApi(self.api_client)
+        self.workflowResourceApi = WorkflowResourceApi(self.api_client)
+        self.applicationResourceApi = ApplicationResourceApi(self.api_client)
+        self.secretResourceApi = SecretResourceApi(self.api_client)
+        self.userResourceApi = UserResourceApi(self.api_client)
+        self.groupResourceApi = GroupResourceApi(self.api_client)
+        self.authorizationResourceApi = AuthorizationResourceApi(self.api_client)
+        self.schedulerResourceApi = SchedulerResourceApi(self.api_client)
+        self.tagsApi = TagsApi(self.api_client)
+        self.integrationApi = IntegrationResourceApi(self.api_client)
+        self.promptApi = PromptResourceApi(self.api_client)
diff --git a/omagent_core/engine/orkes/orkes_integration_client.py b/omagent_core/engine/orkes/orkes_integration_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5ecc86de3e82b759e9cddbfea420417d522a53f
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_integration_client.py
@@ -0,0 +1,111 @@
+from __future__ import absolute_import
+
+from typing import List
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models.integration import Integration
+from omagent_core.engine.http.models.integration_api import IntegrationApi
+from omagent_core.engine.http.models.integration_api_update import \
+    IntegrationApiUpdate
+from omagent_core.engine.http.models.integration_update import \
+    IntegrationUpdate
+from omagent_core.engine.http.models.prompt_template import PromptTemplate
+from omagent_core.engine.http.rest import ApiException
+from omagent_core.engine.integration_client import IntegrationClient
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+
+
+class OrkesIntegrationClient(OrkesBaseClient, IntegrationClient):
+
+    def __init__(self, configuration: Configuration):
+        super(OrkesIntegrationClient, self).__init__(configuration)
+
+    def associate_prompt_with_integration(
+        self, ai_integration: str, model_name: str, prompt_name: str
+    ):
+        self.integrationApi.associate_prompt_with_integration(
+            ai_integration, model_name, prompt_name
+        )
+
+    def delete_integration_api(self, api_name: str, integration_name: str):
+        self.integrationApi.delete_integration_api(api_name, integration_name)
+
+    def delete_integration(self, integration_name: str):
+        self.integrationApi.delete_integration_provider(integration_name)
+
+    def get_integration_api(
+        self, api_name: str, integration_name: str
+    ) -> IntegrationApi:
+        try:
+            return self.integrationApi.get_integration_api(api_name, integration_name)
+        except ApiException as e:
+            if e.is_not_found():
+                return None
+            raise e
+
+    def get_integration_apis(self, integration_name: str) -> List[IntegrationApi]:
+        return self.integrationApi.get_integration_apis(integration_name)
+
+    def get_integration(self, integration_name: str) -> Integration:
+        try:
+            return self.integrationApi.get_integration_provider(integration_name)
+        except ApiException as e:
+            if e.is_not_found():
+                return None
+            raise e
+
+    def get_integrations(self) -> List[Integration]:
+        return self.integrationApi.get_integration_providers()
+
+    def get_prompts_with_integration(
+        self, ai_integration: str, model_name: str
+    ) -> List[PromptTemplate]:
+        return self.integrationApi.get_prompts_with_integration(
+            ai_integration, model_name
+        )
+
+    def save_integration_api(
+        self, integration_name, api_name, api_details: IntegrationApiUpdate
+    ):
+        self.integrationApi.save_integration_api(
+            api_details, integration_name, api_name
+        )
+
+    def save_integration(
+        self, integration_name, integration_details: IntegrationUpdate
+    ):
+        self.integrationApi.save_integration_provider(
+            integration_details, integration_name
+        )
+
+    def get_token_usage_for_integration(self, name, integration_name) -> int:
+        return self.integrationApi.get_token_usage_for_integration(
+            name, integration_name
+        )
+
+    def get_token_usage_for_integration_provider(self, name) -> dict:
+        return self.integrationApi.get_token_usage_for_integration_provider(name)
+
+    def register_token_usage(self, body, name, integration_name):
+        pass
+
+    # Tags
+
+    def delete_tag_for_integration(self, body, tag_name, integration_name):
+        """Delete an integration"""
+        pass
+
+    def delete_tag_for_integration_provider(self, body, name):
+        pass
+
+    def put_tag_for_integration(self, body, name, integration_name):
+        pass
+
+    def put_tag_for_integration_provider(self, body, name):
+        pass
+
+    def get_tags_for_integration(self, name, integration_name):
+        pass
+
+    def get_tags_for_integration_provider(self, name):
+        pass
diff --git a/omagent_core/engine/orkes/orkes_metadata_client.py b/omagent_core/engine/orkes/orkes_metadata_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..c2d95ab568df5a47120696589a46a2ccecccc9df
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_metadata_client.py
@@ -0,0 +1,100 @@
+from typing import List, Optional
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models.tag_string import TagString
+from omagent_core.engine.http.models.task_def import TaskDef
+from omagent_core.engine.http.models.workflow_def import WorkflowDef
+from omagent_core.engine.metadata_client import MetadataClient
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+from omagent_core.engine.orkes.models.ratelimit_tag import RateLimitTag
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+
+
+class OrkesMetadataClient(OrkesBaseClient, MetadataClient):
+    def __init__(self, configuration: Configuration):
+        super(OrkesMetadataClient, self).__init__(configuration)
+
+    def register_workflow_def(
+        self, workflow_def: WorkflowDef, overwrite: Optional[bool] = True
+    ):
+        self.metadataResourceApi.create(workflow_def, overwrite=overwrite)
+
+    def update_workflow_def(
+        self, workflow_def: WorkflowDef, overwrite: Optional[bool] = True
+    ):
+        self.metadataResourceApi.update1([workflow_def], overwrite=overwrite)
+
+    def unregister_workflow_def(self, name: str, version: int):
+        self.metadataResourceApi.unregister_workflow_def(name, version)
+
+    def get_workflow_def(self, name: str, version: Optional[int] = None) -> WorkflowDef:
+        workflow = None
+        if version:
+            workflow = self.metadataResourceApi.get(name, version=version)
+        else:
+            workflow = self.metadataResourceApi.get(name)
+
+        return workflow
+
+    def get_all_workflow_defs(self) -> List[WorkflowDef]:
+        return self.metadataResourceApi.get_all_workflows()
+
+    def register_task_def(self, task_def: TaskDef):
+        self.metadataResourceApi.register_task_def([task_def])
+
+    def update_task_def(self, task_def: TaskDef):
+        self.metadataResourceApi.update_task_def(task_def)
+
+    def unregister_task_def(self, task_type: str):
+        self.metadataResourceApi.unregister_task_def(task_type)
+
+    def get_task_def(self, task_type: str) -> TaskDef:
+        return self.metadataResourceApi.get_task_def(task_type)
+
+    def get_all_task_defs(self) -> List[TaskDef]:
+        return self.metadataResourceApi.get_task_defs()
+
+    def add_workflow_tag(self, tag: MetadataTag, workflow_name: str):
+        self.tagsApi.add_workflow_tag(tag, workflow_name)
+
+    def delete_workflow_tag(self, tag: MetadataTag, workflow_name: str):
+        tagStr = TagString(tag.key, tag.type, tag.value)
+        self.tagsApi.delete_workflow_tag(tagStr, workflow_name)
+
+    def get_workflow_tags(self, workflow_name: str) -> List[MetadataTag]:
+        return self.tagsApi.get_workflow_tags(workflow_name)
+
+    def set_workflow_tags(self, tags: List[MetadataTag], workflow_name: str):
+        self.tagsApi.set_workflow_tags(tags, workflow_name)
+
+    def addTaskTag(self, tag: MetadataTag, taskName: str):
+        self.tagsApi.add_task_tag(tag, taskName)
+
+    def deleteTaskTag(self, tag: MetadataTag, taskName: str):
+        tagStr = TagString(tag.key, tag.type, tag.value)
+        self.tagsApi.delete_task_tag(tagStr, taskName)
+
+    def getTaskTags(self, taskName: str) -> List[MetadataTag]:
+        return self.tagsApi.get_task_tags(taskName)
+
+    def setTaskTags(self, tags: List[MetadataTag], taskName: str):
+        self.tagsApi.set_task_tags(tags, taskName)
+
+    def setWorkflowRateLimit(self, rateLimit: int, workflowName: str):
+        self.removeWorkflowRateLimit(workflowName)
+        rateLimitTag = RateLimitTag(workflowName, rateLimit)
+        self.tagsApi.add_workflow_tag(rateLimitTag, workflowName)
+
+    def getWorkflowRateLimit(self, workflowName: str) -> Optional[int]:
+        tags = self.tagsApi.get_workflow_tags(workflowName)
+        for tag in tags:
+            if tag.type == "RATE_LIMIT" and tag.key == workflowName:
+                return tag.value
+
+        return None
+
+    def removeWorkflowRateLimit(self, workflowName: str):
+        current_rate_limit = self.getWorkflowRateLimit(workflowName)
+        if current_rate_limit:
+            rateLimitTag = RateLimitTag(workflowName, current_rate_limit)
+            self.tagsApi.delete_workflow_tag(rateLimitTag, workflowName)
diff --git a/omagent_core/engine/orkes/orkes_prompt_client.py b/omagent_core/engine/orkes/orkes_prompt_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..24887ed9a83b774a63c25c73d7ccf2cfa9d21ff1
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_prompt_client.py
@@ -0,0 +1,68 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+from typing import List
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models.prompt_template import PromptTemplate
+from omagent_core.engine.http.models.prompt_test_request import \
+    PromptTemplateTestRequest
+from omagent_core.engine.http.rest import ApiException
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+from omagent_core.engine.prompt_client import PromptClient
+
+# python 2 and python 3 compatibility library
+
+
+class OrkesPromptClient(OrkesBaseClient, PromptClient):
+
+    def __init__(self, configuration: Configuration):
+        super(OrkesPromptClient, self).__init__(configuration)
+
+    def save_prompt(self, prompt_name: str, description: str, prompt_template: str):
+        self.promptApi.save_message_template(prompt_template, description, prompt_name)
+
+    def get_prompt(self, prompt_name: str) -> PromptTemplate:
+        try:
+            return self.promptApi.get_message_template(prompt_name)
+        except ApiException as e:
+            if e.is_not_found():
+                return None
+            raise e
+
+    def get_prompts(self):
+        return self.promptApi.get_message_templates()
+
+    def delete_prompt(self, prompt_name: str):
+        self.promptApi.delete_message_template(prompt_name)
+
+    def get_tags_for_prompt_template(self, prompt_name: str) -> List[MetadataTag]:
+        self.promptApi.get_tags_for_prompt_template(prompt_name)
+
+    def update_tag_for_prompt_template(self, prompt_name: str, tags: List[MetadataTag]):
+        self.promptApi.put_tag_for_prompt_template(tags, prompt_name)
+
+    def delete_tag_for_prompt_template(self, prompt_name: str, tags: List[MetadataTag]):
+        self.promptApi.delete_tag_for_prompt_template(tags, prompt_name)
+
+    def test_prompt(
+        self,
+        prompt_text: str,
+        variables: dict,
+        ai_integration: str,
+        text_complete_model: str,
+        temperature: float = 0.1,
+        top_p: float = 0.9,
+        stop_words: List[str] = None,
+    ) -> str:
+        request = PromptTemplateTestRequest()
+        request.prompt = prompt_text
+        request.llm_provider = ai_integration
+        request.model = text_complete_model
+        request.prompt_variables = variables
+        request.temperature = temperature
+        request.top_p = top_p
+        if stop_words is not None:
+            request.stop_words = stop_words
+        return self.promptApi.test_message_template(request)
diff --git a/omagent_core/engine/orkes/orkes_scheduler_client.py b/omagent_core/engine/orkes/orkes_scheduler_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..ab2461f2d59f4ffd116e492adfeded5a6cf2102c
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_scheduler_client.py
@@ -0,0 +1,99 @@
+from typing import List, Optional
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models.save_schedule_request import \
+    SaveScheduleRequest
+from omagent_core.engine.http.models.search_result_workflow_schedule_execution_model import \
+    SearchResultWorkflowScheduleExecutionModel
+from omagent_core.engine.http.models.workflow_schedule import WorkflowSchedule
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+from omagent_core.engine.scheduler_client import SchedulerClient
+
+
+class OrkesSchedulerClient(OrkesBaseClient, SchedulerClient):
+    def __init__(self, configuration: Configuration):
+        super(OrkesSchedulerClient, self).__init__(configuration)
+
+    def save_schedule(self, save_schedule_request: SaveScheduleRequest):
+        self.schedulerResourceApi.save_schedule(save_schedule_request)
+
+    def get_schedule(self, name: str) -> WorkflowSchedule:
+        return self.schedulerResourceApi.get_schedule(name)
+
+    def get_all_schedules(
+        self, workflow_name: Optional[str] = None
+    ) -> List[WorkflowSchedule]:
+        kwargs = {}
+        if workflow_name:
+            kwargs.update({"workflow_name": workflow_name})
+
+        return self.schedulerResourceApi.get_all_schedules(**kwargs)
+
+    def get_next_few_schedule_execution_times(
+        self,
+        cron_expression: str,
+        schedule_start_time: Optional[int] = None,
+        schedule_end_time: Optional[int] = None,
+        limit: Optional[int] = None,
+    ) -> List[int]:
+        kwargs = {}
+        if schedule_start_time:
+            kwargs.update({"schedule_start_time": schedule_start_time})
+        if schedule_end_time:
+            kwargs.update({"schedule_end_time": schedule_end_time})
+        if limit:
+            kwargs.update({"limit": limit})
+        return self.schedulerResourceApi.get_next_few_schedules(
+            cron_expression, **kwargs
+        )
+
+    def delete_schedule(self, name: str):
+        self.schedulerResourceApi.delete_schedule(name)
+
+    def pause_schedule(self, name: str):
+        self.schedulerResourceApi.pause_schedule(name)
+
+    def pause_all_schedules(self):
+        self.schedulerResourceApi.pause_all_schedules()
+
+    def resume_schedule(self, name: str):
+        self.schedulerResourceApi.resume_schedule(name)
+
+    def resume_all_schedules(self):
+        self.schedulerResourceApi.resume_all_schedules()
+
+    def search_schedule_executions(
+        self,
+        start: Optional[int] = None,
+        size: Optional[int] = None,
+        sort: Optional[str] = None,
+        free_text: Optional[str] = None,
+        query: Optional[str] = None,
+    ) -> SearchResultWorkflowScheduleExecutionModel:
+        kwargs = {}
+        if start:
+            kwargs.update({"start": start})
+        if size:
+            kwargs.update({"size": size})
+        if sort:
+            kwargs.update({"sort": sort})
+        if free_text:
+            kwargs.update({"freeText": free_text})
+        if query:
+            kwargs.update({"query": query})
+        return self.schedulerResourceApi.search_v21(**kwargs)
+
+    def requeue_all_execution_records(self):
+        self.schedulerResourceApi.requeue_all_execution_records()
+
+    def set_scheduler_tags(self, tags: List[MetadataTag], name: str):
+        self.schedulerResourceApi.put_tag_for_schedule(tags, name)
+
+    def get_scheduler_tags(self, name: str) -> List[MetadataTag]:
+        return self.schedulerResourceApi.get_tags_for_schedule(name)
+
+    def delete_scheduler_tags(
+        self, tags: List[MetadataTag], name: str
+    ) -> List[MetadataTag]:
+        self.schedulerResourceApi.delete_tag_for_schedule(tags, name)
diff --git a/omagent_core/engine/orkes/orkes_secret_client.py b/omagent_core/engine/orkes/orkes_secret_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..0161eeadd037bb9c6953f9c1f9f9802c5a5e1c0b
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_secret_client.py
@@ -0,0 +1,40 @@
+from typing import List
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+from omagent_core.engine.secret_client import SecretClient
+
+
+class OrkesSecretClient(OrkesBaseClient, SecretClient):
+    def __init__(self, configuration: Configuration):
+        super(OrkesSecretClient, self).__init__(configuration)
+
+    def put_secret(self, key: str, value: str):
+        self.secretResourceApi.put_secret(value, key)
+
+    def get_secret(self, key: str) -> str:
+        return self.secretResourceApi.get_secret(key)
+
+    def list_all_secret_names(self) -> set[str]:
+        return set(self.secretResourceApi.list_all_secret_names())
+
+    def list_secrets_that_user_can_grant_access_to(self) -> List[str]:
+        return self.secretResourceApi.list_secrets_that_user_can_grant_access_to()
+
+    def delete_secret(self, key: str):
+        self.secretResourceApi.delete_secret(key)
+
+    def secret_exists(self, key: str) -> bool:
+        return self.secretResourceApi.secret_exists(key)
+
+    def set_secret_tags(self, tags: List[MetadataTag], key: str):
+        self.secretResourceApi.put_tag_for_secret(tags, key)
+
+    def get_secret_tags(self, key: str) -> List[MetadataTag]:
+        return self.secretResourceApi.get_tags(key)
+
+    def delete_secret_tags(
+        self, tags: List[MetadataTag], key: str
+    ) -> List[MetadataTag]:
+        self.secretResourceApi.delete_tag_for_secret(tags, key)
diff --git a/omagent_core/engine/orkes/orkes_task_client.py b/omagent_core/engine/orkes/orkes_task_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..3796e2c30823f6f23e7aef933d63ae3b93be2fc0
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_task_client.py
@@ -0,0 +1,103 @@
+from typing import List, Optional
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models import PollData
+from omagent_core.engine.http.models.task import Task
+from omagent_core.engine.http.models.task_exec_log import TaskExecLog
+from omagent_core.engine.http.models.task_result import TaskResult
+from omagent_core.engine.http.models.workflow import Workflow
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+from omagent_core.engine.task_client import TaskClient
+
+
+class OrkesTaskClient(OrkesBaseClient, TaskClient):
+    def __init__(self, configuration: Configuration):
+        super(OrkesTaskClient, self).__init__(configuration)
+
+    def poll_task(
+        self,
+        task_type: str,
+        worker_id: Optional[str] = None,
+        domain: Optional[str] = None,
+    ) -> Optional[Task]:
+        kwargs = {}
+        if worker_id:
+            kwargs.update({"workerid": worker_id})
+        if domain:
+            kwargs.update({"domain": domain})
+
+        return self.taskResourceApi.poll(task_type, **kwargs)
+
+    def batch_poll_tasks(
+        self,
+        task_type: str,
+        worker_id: Optional[str] = None,
+        count: Optional[int] = None,
+        timeout_in_millisecond: Optional[int] = None,
+        domain: Optional[str] = None,
+    ) -> List[Task]:
+        kwargs = {}
+        if worker_id:
+            kwargs.update({"workerid": worker_id})
+        if count:
+            kwargs.update({"count": count})
+        if timeout_in_millisecond:
+            kwargs.update({"timeout": timeout_in_millisecond})
+        if domain:
+            kwargs.update({"domain": domain})
+
+        return self.taskResourceApi.batch_poll(task_type, **kwargs)
+
+    def get_task(self, task_id: str) -> Task:
+        return self.taskResourceApi.get_task(task_id)
+
+    def update_task(self, task_result: TaskResult) -> str:
+        return self.taskResourceApi.update_task(task_result)
+
+    def update_task_by_ref_name(
+        self,
+        workflow_id: str,
+        task_ref_name: str,
+        status: str,
+        output: object,
+        worker_id: Optional[str] = None,
+    ) -> str:
+        body = {"result": output}
+        kwargs = {}
+        if worker_id:
+            kwargs.update({"workerid": worker_id})
+        return self.taskResourceApi.update_task1(
+            body, workflow_id, task_ref_name, status, **kwargs
+        )
+
+    def update_task_sync(
+        self,
+        workflow_id: str,
+        task_ref_name: str,
+        status: str,
+        output: object,
+        worker_id: Optional[str] = None,
+    ) -> Workflow:
+        if not isinstance(output, dict):
+            output = {"result": output}
+        body = output
+        kwargs = {}
+        if worker_id:
+            kwargs.update({"workerid": worker_id})
+        return self.taskResourceApi.update_task_sync(
+            body, workflow_id, task_ref_name, status, **kwargs
+        )
+
+    def get_queue_size_for_task(self, task_type: str) -> int:
+        queueSizesByTaskType = self.taskResourceApi.size(task_type=[task_type])
+        queueSize = queueSizesByTaskType.get(task_type, 0)
+        return queueSize
+
+    def add_task_log(self, task_id: str, log_message: str):
+        self.taskResourceApi.log(log_message, task_id)
+
+    def get_task_logs(self, task_id: str) -> List[TaskExecLog]:
+        return self.taskResourceApi.get_task_logs(task_id)
+
+    def get_task_poll_data(self, task_type: str) -> List[PollData]:
+        return self.taskResourceApi.get_poll_data(task_type=task_type)
diff --git a/omagent_core/engine/orkes/orkes_workflow_client.py b/omagent_core/engine/orkes/orkes_workflow_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..b13c5a3842ad36cee31cfa3cff22e2a0caecae93
--- /dev/null
+++ b/omagent_core/engine/orkes/orkes_workflow_client.py
@@ -0,0 +1,227 @@
+from typing import List, Optional
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.models import (
+    ScrollableSearchResultWorkflowSummary, SkipTaskRequest, WorkflowStatus)
+from omagent_core.engine.http.models.correlation_ids_search_request import \
+    CorrelationIdsSearchRequest
+from omagent_core.engine.http.models.rerun_workflow_request import \
+    RerunWorkflowRequest
+from omagent_core.engine.http.models.start_workflow_request import \
+    StartWorkflowRequest
+from omagent_core.engine.http.models.workflow import Workflow
+from omagent_core.engine.http.models.workflow_run import WorkflowRun
+from omagent_core.engine.http.models.workflow_state_update import \
+    WorkflowStateUpdate
+from omagent_core.engine.http.models.workflow_test_request import \
+    WorkflowTestRequest
+from omagent_core.engine.orkes.orkes_base_client import OrkesBaseClient
+from omagent_core.engine.workflow_client import WorkflowClient
+from omagent_core.utils.container import container
+import os
+
+class OrkesWorkflowClient(OrkesBaseClient, WorkflowClient):
+    def __init__(self, configuration: Configuration):
+        super(OrkesWorkflowClient, self).__init__(configuration)
+
+    def check_connection(self) -> bool:
+        """Check if the connection to Conductor server is valid"""
+        self.metadataResourceApi.get_all_workflows()
+
+    def start_workflow_by_name(
+        self,
+        name: str,
+        input: dict[str, object],
+        version: Optional[int] = None,
+        **kwargs
+    ) -> str:
+        start_workflow_request = StartWorkflowRequest(
+            name=name, version=version, input=input, **kwargs
+        )
+
+        return self.start_workflow(start_workflow_request)
+
+    def start_workflow(self, start_workflow_request: StartWorkflowRequest) -> str:
+        return self.workflowResourceApi.start_workflow(start_workflow_request)
+
+    def execute_workflow(
+        self,
+        start_workflow_request: StartWorkflowRequest,
+        request_id: str = None,
+        wait_until_task_ref: Optional[str] = None,
+        wait_for_seconds: int = 30,
+    ) -> WorkflowRun:
+
+        return self.workflowResourceApi.execute_workflow(
+            body=start_workflow_request,
+            request_id=request_id,
+            version=start_workflow_request.version,
+            name=start_workflow_request.name,
+            wait_until_task_ref=wait_until_task_ref,
+            wait_for_seconds=wait_for_seconds,
+        )
+
+    def pause_workflow(self, workflow_id: str):
+        self.workflowResourceApi.pause_workflow(workflow_id)
+
+    def resume_workflow(self, workflow_id: str):
+        self.workflowResourceApi.resume_workflow(workflow_id)
+
+    def restart_workflow(
+        self, workflow_id: str, use_latest_def: Optional[bool] = False
+    ):
+        kwargs = {}
+        if use_latest_def:
+            kwargs["use_latest_definitions"] = use_latest_def
+        self.workflowResourceApi.restart(workflow_id, **kwargs)
+
+    def rerun_workflow(
+        self, workflow_id: str, rerun_workflow_request: RerunWorkflowRequest
+    ) -> str:
+        rerun_workflow_request.re_run_from_workflow_id = workflow_id
+        return self.workflowResourceApi.rerun(rerun_workflow_request, workflow_id)
+
+    def retry_workflow(
+        self, workflow_id: str, resume_subworkflow_tasks: Optional[bool] = False
+    ):
+        kwargs = {}
+        if resume_subworkflow_tasks:
+            kwargs["resume_subworkflow_tasks"] = resume_subworkflow_tasks
+        self.workflowResourceApi.retry(workflow_id, **kwargs)
+
+    def terminate_workflow(
+        self,
+        workflow_id: str,
+        reason: Optional[str] = None,
+        trigger_failure_workflow: bool = False,
+    ):
+        kwargs = {}
+        if reason:
+            kwargs["reason"] = reason
+        if trigger_failure_workflow:
+            kwargs["trigger_failure_workflow"] = trigger_failure_workflow
+        self.workflowResourceApi.terminate(workflow_id, **kwargs)
+
+    def get_workflow(
+        self, workflow_id: str, include_tasks: Optional[bool] = True
+    ) -> Workflow:
+        kwargs = {}
+        if include_tasks:
+            kwargs["include_tasks"] = include_tasks
+        return self.workflowResourceApi.get_execution_status(workflow_id, **kwargs)
+
+    def get_workflow_status(
+        self,
+        workflow_id: str,
+        include_output: bool = None,
+        include_variables: bool = None,
+    ) -> WorkflowStatus:
+        if os.getenv("OMAGENT_MODE") == "lite":
+            return WorkflowStatus(status="RUNNING")
+        kwargs = {}
+        if include_output is not None:
+            kwargs["include_output"] = include_output
+        if include_variables is not None:
+            kwargs["include_variables"] = include_variables
+        return self.workflowResourceApi.get_workflow_status_summary(
+            workflow_id, **kwargs
+        )
+
+    def delete_workflow(
+        self, workflow_id: str, archive_workflow: Optional[bool] = True
+    ):
+        self.workflowResourceApi.delete(workflow_id, archive_workflow=archive_workflow)
+
+    def skip_task_from_workflow(
+        self, workflow_id: str, task_reference_name: str, request: SkipTaskRequest
+    ):
+        self.workflowResourceApi.skip_task_from_workflow(
+            workflow_id, task_reference_name, request
+        )
+
+    def test_workflow(self, test_request: WorkflowTestRequest) -> Workflow:
+        return self.workflowResourceApi.test_workflow(test_request)
+
+    def search(
+        self,
+        start: int = 0,
+        size: int = 100,
+        free_text: str = "*",
+        query: str = None,
+        query_id: str = None,
+    ) -> ScrollableSearchResultWorkflowSummary:
+        args = {
+            "start": start,
+            "size": size,
+            "free_text": free_text,
+            "query": query,
+            "query_id": query_id,
+        }
+        return self.workflowResourceApi.search(**args)
+
+    def get_by_correlation_ids_in_batch(
+        self,
+        batch_request: CorrelationIdsSearchRequest,
+        include_completed: bool = False,
+        include_tasks: bool = False,
+    ) -> dict[str, List[Workflow]]:
+        """Given the list of correlation ids and list of workflow names, find and return workflows
+        Returns a map with key as correlationId and value as a list of Workflows
+        When IncludeClosed is set to true, the return value also includes workflows that are completed otherwise only running workflows are returned
+        """
+        kwargs = {}
+
+        kwargs["body"] = batch_request
+        if include_tasks:
+            kwargs["include_tasks"] = include_tasks
+        if include_completed:
+            kwargs["include_closed"] = include_completed
+        return self.workflowResourceApi.get_workflows_by_correlation_id_in_batch(
+            **kwargs
+        )
+
+    def get_by_correlation_ids(
+        self,
+        workflow_name: str,
+        correlation_ids: List[str],
+        include_completed: bool = False,
+        include_tasks: bool = False,
+    ) -> dict[str, List[Workflow]]:
+        """Lists workflows for the given correlation id list"""
+        kwargs = {}
+        if include_tasks:
+            kwargs["include_tasks"] = include_tasks
+        if include_completed:
+            kwargs["include_closed"] = include_completed
+
+        return self.workflowResourceApi.get_workflows(
+            body=correlation_ids, name=workflow_name, **kwargs
+        )
+
+    def remove_workflow(self, workflow_id: str):
+        self.workflowResourceApi.delete(workflow_id)
+
+    def update_variables(
+        self, workflow_id: str, variables: dict[str, object] = {}
+    ) -> None:
+        self.workflowResourceApi.update_workflow_state(variables, workflow_id)
+
+    def update_state(
+        self,
+        workflow_id: str,
+        update_requesst: WorkflowStateUpdate,
+        wait_until_task_ref_names: List[str] = None,
+        wait_for_seconds: int = None,
+    ) -> WorkflowRun:
+        kwargs = {}
+        if wait_until_task_ref_names is not None:
+            kwargs["wait_until_task_ref"] = ",".join(wait_until_task_ref_names)
+        if wait_for_seconds is not None:
+            kwargs["wait_for_seconds"] = wait_for_seconds
+
+        return self.workflowResourceApi.update_workflow_and_task_state(
+            update_requesst=update_requesst, workflow_id=workflow_id, **kwargs
+        )
+
+
+workflow_client = OrkesWorkflowClient(container.conductor_config)
diff --git a/omagent_core/engine/orkes_clients.py b/omagent_core/engine/orkes_clients.py
new file mode 100644
index 0000000000000000000000000000000000000000..73d36ff5cb5ef253aa050d4a52802e8a7c010bea
--- /dev/null
+++ b/omagent_core/engine/orkes_clients.py
@@ -0,0 +1,57 @@
+from omagent_core.engine.authorization_client import AuthorizationClient
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.integration_client import IntegrationClient
+from omagent_core.engine.metadata_client import MetadataClient
+from omagent_core.engine.orkes.orkes_authorization_client import \
+    OrkesAuthorizationClient
+from omagent_core.engine.orkes.orkes_integration_client import \
+    OrkesIntegrationClient
+from omagent_core.engine.orkes.orkes_metadata_client import OrkesMetadataClient
+from omagent_core.engine.orkes.orkes_prompt_client import OrkesPromptClient
+from omagent_core.engine.orkes.orkes_scheduler_client import \
+    OrkesSchedulerClient
+from omagent_core.engine.orkes.orkes_secret_client import OrkesSecretClient
+from omagent_core.engine.orkes.orkes_task_client import OrkesTaskClient
+from omagent_core.engine.orkes.orkes_workflow_client import OrkesWorkflowClient
+from omagent_core.engine.prompt_client import PromptClient
+from omagent_core.engine.scheduler_client import SchedulerClient
+from omagent_core.engine.secret_client import SecretClient
+from omagent_core.engine.task_client import TaskClient
+from omagent_core.engine.workflow.executor.workflow_executor import \
+    WorkflowExecutor
+from omagent_core.engine.workflow_client import WorkflowClient
+from omagent_core.utils.container import container
+
+
+class OrkesClients:
+    def __init__(self, configuration: Configuration = None):
+        if configuration is None:
+            configuration = container.conductor_config
+        self.configuration = configuration
+
+    def get_workflow_client(self) -> WorkflowClient:
+        return OrkesWorkflowClient(self.configuration)
+
+    def get_authorization_client(self) -> AuthorizationClient:
+        return OrkesAuthorizationClient(self.configuration)
+
+    def get_metadata_client(self) -> MetadataClient:
+        return OrkesMetadataClient(self.configuration)
+
+    def get_scheduler_client(self) -> SchedulerClient:
+        return OrkesSchedulerClient(self.configuration)
+
+    def get_secret_client(self) -> SecretClient:
+        return OrkesSecretClient(self.configuration)
+
+    def get_task_client(self) -> TaskClient:
+        return OrkesTaskClient(self.configuration)
+
+    def get_integration_client(self) -> IntegrationClient:
+        return OrkesIntegrationClient(self.configuration)
+
+    def get_workflow_executor(self) -> WorkflowExecutor:
+        return WorkflowExecutor(self.configuration)
+
+    def get_prompt_client(self) -> PromptClient:
+        return OrkesPromptClient(self.configuration)
diff --git a/omagent_core/engine/prompt_client.py b/omagent_core/engine/prompt_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..55759036e4ea51bdc583a350c26820ba5df42152
--- /dev/null
+++ b/omagent_core/engine/prompt_client.py
@@ -0,0 +1,55 @@
+from __future__ import absolute_import
+
+import re  # noqa: F401
+from abc import ABC, abstractmethod
+from typing import List
+
+# python 2 and python 3 compatibility library
+import six
+from omagent_core.engine.http.api_client import ApiClient
+from omagent_core.engine.http.models.prompt_template import PromptTemplate
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+
+
+class PromptClient(ABC):
+
+    @abstractmethod
+    def save_prompt(self, prompt_name: str, description: str, prompt_template: str):
+        pass
+
+    @abstractmethod
+    def get_prompt(self, prompt_name: str) -> PromptTemplate:
+        pass
+
+    @abstractmethod
+    def get_prompts(self):
+        pass
+
+    @abstractmethod
+    def delete_prompt(self, prompt_name: str):
+        pass
+
+    @abstractmethod
+    def get_tags_for_prompt_template(self, prompt_name: str) -> List[MetadataTag]:
+        pass
+
+    @abstractmethod
+    def update_tag_for_prompt_template(self, prompt_name: str, tags: List[MetadataTag]):
+        pass
+
+    @abstractmethod
+    def delete_tag_for_prompt_template(self, prompt_name: str, tags: List[MetadataTag]):
+        pass
+
+    @abstractmethod
+    def test_prompt(
+        self,
+        prompt_text: str,
+        variables: dict,
+        ai_integration: str,
+        text_complete_model: str,
+        temperature: float = 0.1,
+        top_p: float = 0.9,
+        stop_words: List[str] = None,
+    ) -> str:
+        pass
diff --git a/omagent_core/engine/scheduler_client.py b/omagent_core/engine/scheduler_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..c141950036ede9542a7784976dcdebd451bc3864
--- /dev/null
+++ b/omagent_core/engine/scheduler_client.py
@@ -0,0 +1,84 @@
+from abc import ABC, abstractmethod
+from typing import List, Optional
+
+from omagent_core.engine.http.models.save_schedule_request import \
+    SaveScheduleRequest
+from omagent_core.engine.http.models.search_result_workflow_schedule_execution_model import \
+    SearchResultWorkflowScheduleExecutionModel
+from omagent_core.engine.http.models.workflow_schedule import WorkflowSchedule
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+
+
+class SchedulerClient(ABC):
+    @abstractmethod
+    def save_schedule(self, save_schedule_request: SaveScheduleRequest):
+        pass
+
+    @abstractmethod
+    def get_schedule(self, name: str) -> (Optional[WorkflowSchedule], str):
+        pass
+
+    @abstractmethod
+    def get_all_schedules(
+        self, workflow_name: Optional[str] = None
+    ) -> List[WorkflowSchedule]:
+        pass
+
+    @abstractmethod
+    def get_next_few_schedule_execution_times(
+        self,
+        cron_expression: str,
+        schedule_start_time: Optional[int] = None,
+        schedule_end_time: Optional[int] = None,
+        limit: Optional[int] = None,
+    ) -> List[int]:
+        pass
+
+    @abstractmethod
+    def delete_schedule(self, name: str):
+        pass
+
+    @abstractmethod
+    def pause_schedule(self, name: str):
+        pass
+
+    @abstractmethod
+    def pause_all_schedules(self):
+        pass
+
+    @abstractmethod
+    def resume_schedule(self, name: str):
+        pass
+
+    @abstractmethod
+    def resume_all_schedules(self):
+        pass
+
+    @abstractmethod
+    def search_schedule_executions(
+        self,
+        start: Optional[int] = None,
+        size: Optional[int] = None,
+        sort: Optional[str] = None,
+        free_text: Optional[str] = None,
+        query: Optional[str] = None,
+    ) -> SearchResultWorkflowScheduleExecutionModel:
+        pass
+
+    @abstractmethod
+    def requeue_all_execution_records(self):
+        pass
+
+    @abstractmethod
+    def set_scheduler_tags(self, tags: List[MetadataTag], name: str):
+        pass
+
+    @abstractmethod
+    def get_scheduler_tags(self, name: str) -> List[MetadataTag]:
+        pass
+
+    @abstractmethod
+    def delete_scheduler_tags(
+        self, tags: List[MetadataTag], name: str
+    ) -> List[MetadataTag]:
+        pass
diff --git a/omagent_core/engine/secret_client.py b/omagent_core/engine/secret_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..a76b1344ccbd12283e45791f0dff6e00464a6b88
--- /dev/null
+++ b/omagent_core/engine/secret_client.py
@@ -0,0 +1,44 @@
+from abc import ABC, abstractmethod
+from typing import List
+
+from omagent_core.engine.orkes.models.metadata_tag import MetadataTag
+
+
+class SecretClient(ABC):
+    @abstractmethod
+    def put_secret(self, key: str, value: str):
+        pass
+
+    @abstractmethod
+    def get_secret(self, key: str) -> str:
+        pass
+
+    @abstractmethod
+    def list_all_secret_names(self) -> set[str]:
+        pass
+
+    @abstractmethod
+    def list_secrets_that_user_can_grant_access_to(self) -> List[str]:
+        pass
+
+    @abstractmethod
+    def delete_secret(self, key: str):
+        pass
+
+    @abstractmethod
+    def secret_exists(self, key: str) -> bool:
+        pass
+
+    @abstractmethod
+    def set_secret_tags(self, tags: List[MetadataTag], key: str):
+        pass
+
+    @abstractmethod
+    def get_secret_tags(self, key: str) -> List[MetadataTag]:
+        pass
+
+    @abstractmethod
+    def delete_secret_tags(
+        self, tags: List[MetadataTag], key: str
+    ) -> List[MetadataTag]:
+        pass
diff --git a/omagent_core/engine/task_client.py b/omagent_core/engine/task_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..3bedabadc3b0537727ccb6f7ea157156c7aa0299
--- /dev/null
+++ b/omagent_core/engine/task_client.py
@@ -0,0 +1,77 @@
+from abc import ABC, abstractmethod
+from typing import List, Optional
+
+from omagent_core.engine.http.models import PollData
+from omagent_core.engine.http.models.task import Task
+from omagent_core.engine.http.models.task_exec_log import TaskExecLog
+from omagent_core.engine.http.models.task_result import TaskResult
+from omagent_core.engine.http.models.task_result_status import TaskResultStatus
+from omagent_core.engine.http.models.workflow import Workflow
+
+
+class TaskClient(ABC):
+    @abstractmethod
+    def poll_task(
+        self,
+        task_type: str,
+        worker_id: Optional[str] = None,
+        domain: Optional[str] = None,
+    ) -> Optional[Task]:
+        pass
+
+    @abstractmethod
+    def batch_poll_tasks(
+        self,
+        task_type: str,
+        worker_id: Optional[str] = None,
+        count: Optional[int] = None,
+        timeout_in_millisecond: Optional[int] = None,
+        domain: Optional[str] = None,
+    ) -> List[Task]:
+        pass
+
+    @abstractmethod
+    def get_task(self, task_id: str) -> Task:
+        pass
+
+    @abstractmethod
+    def update_task(self, task_result: TaskResult) -> str:
+        pass
+
+    @abstractmethod
+    def update_task_by_ref_name(
+        self,
+        workflow_id: str,
+        task_ref_name: str,
+        status: TaskResultStatus,
+        output: object,
+        worker_id: Optional[str] = None,
+    ) -> str:
+        pass
+
+    @abstractmethod
+    def update_task_sync(
+        self,
+        workflow_id: str,
+        task_ref_name: str,
+        status: TaskResultStatus,
+        output: object,
+        worker_id: Optional[str] = None,
+    ) -> Workflow:
+        pass
+
+    @abstractmethod
+    def get_queue_size_for_task(self, task_type: str) -> int:
+        pass
+
+    @abstractmethod
+    def add_task_log(self, task_id: str, log_message: str):
+        pass
+
+    @abstractmethod
+    def get_task_logs(self, task_id: str) -> List[TaskExecLog]:
+        pass
+
+    @abstractmethod
+    def get_task_poll_data(self, task_type: str) -> List[PollData]:
+        pass
diff --git a/omagent_core/engine/telemetry/__init__.py b/omagent_core/engine/telemetry/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/telemetry/metrics_collector.py b/omagent_core/engine/telemetry/metrics_collector.py
new file mode 100644
index 0000000000000000000000000000000000000000..19f9fa7125aa0f2ede48a400fa6e87a5fcdca0dc
--- /dev/null
+++ b/omagent_core/engine/telemetry/metrics_collector.py
@@ -0,0 +1,257 @@
+import logging
+import os
+import time
+from typing import Any, Dict, List
+
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.configuration.settings.metrics_settings import \
+    MetricsSettings
+from omagent_core.engine.telemetry.model.metric_documentation import \
+    MetricDocumentation
+from omagent_core.engine.telemetry.model.metric_label import MetricLabel
+from omagent_core.engine.telemetry.model.metric_name import MetricName
+from prometheus_client import (CollectorRegistry, Counter, Gauge,
+                               write_to_textfile)
+from prometheus_client.multiprocess import MultiProcessCollector
+
+logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+
+class MetricsCollector:
+    counters = {}
+    gauges = {}
+    registry = CollectorRegistry()
+    must_collect_metrics = False
+
+    def __init__(self, settings: MetricsSettings):
+        if settings != None:
+            os.environ["PROMETHEUS_MULTIPROC_DIR"] = settings.directory
+            MultiProcessCollector(self.registry)
+            self.must_collect_metrics = True
+
+    @staticmethod
+    def provide_metrics(settings: MetricsSettings) -> None:
+        if settings == None:
+            return
+        OUTPUT_FILE_PATH = os.path.join(settings.directory, settings.file_name)
+        registry = CollectorRegistry()
+        MultiProcessCollector(registry)
+        while True:
+            write_to_textfile(OUTPUT_FILE_PATH, registry)
+            time.sleep(settings.update_interval)
+
+    def increment_task_poll(self, task_type: str) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_POLL,
+            documentation=MetricDocumentation.TASK_POLL,
+            labels={MetricLabel.TASK_TYPE: task_type},
+        )
+
+    def increment_task_execution_queue_full(self, task_type: str) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_EXECUTION_QUEUE_FULL,
+            documentation=MetricDocumentation.TASK_EXECUTION_QUEUE_FULL,
+            labels={MetricLabel.TASK_TYPE: task_type},
+        )
+
+    def increment_uncaught_exception(self):
+        self.__increment_counter(
+            name=MetricName.THREAD_UNCAUGHT_EXCEPTION,
+            documentation=MetricDocumentation.THREAD_UNCAUGHT_EXCEPTION,
+            labels={},
+        )
+
+    def increment_task_poll_error(self, task_type: str, exception: Exception) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_POLL_ERROR,
+            documentation=MetricDocumentation.TASK_POLL_ERROR,
+            labels={
+                MetricLabel.TASK_TYPE: task_type,
+                MetricLabel.EXCEPTION: str(exception),
+            },
+        )
+
+    def increment_task_paused(self, task_type: str) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_PAUSED,
+            documentation=MetricDocumentation.TASK_PAUSED,
+            labels={MetricLabel.TASK_TYPE: task_type},
+        )
+
+    def increment_task_execution_error(
+        self, task_type: str, exception: Exception
+    ) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_EXECUTE_ERROR,
+            documentation=MetricDocumentation.TASK_EXECUTE_ERROR,
+            labels={
+                MetricLabel.TASK_TYPE: task_type,
+                MetricLabel.EXCEPTION: str(exception),
+            },
+        )
+
+    def increment_task_ack_failed(self, task_type: str) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_ACK_FAILED,
+            documentation=MetricDocumentation.TASK_ACK_FAILED,
+            labels={MetricLabel.TASK_TYPE: task_type},
+        )
+
+    def increment_task_ack_error(self, task_type: str, exception: Exception) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_ACK_ERROR,
+            documentation=MetricDocumentation.TASK_ACK_ERROR,
+            labels={
+                MetricLabel.TASK_TYPE: task_type,
+                MetricLabel.EXCEPTION: str(exception),
+            },
+        )
+
+    def increment_task_update_error(self, task_type: str, exception: Exception) -> None:
+        self.__increment_counter(
+            name=MetricName.TASK_UPDATE_ERROR,
+            documentation=MetricDocumentation.TASK_UPDATE_ERROR,
+            labels={
+                MetricLabel.TASK_TYPE: task_type,
+                MetricLabel.EXCEPTION: str(exception),
+            },
+        )
+
+    def increment_external_payload_used(
+        self, entity_name: str, operation: str, payload_type: str
+    ) -> None:
+        self.__increment_counter(
+            name=MetricName.EXTERNAL_PAYLOAD_USED,
+            documentation=MetricDocumentation.EXTERNAL_PAYLOAD_USED,
+            labels={
+                MetricLabel.ENTITY_NAME: entity_name,
+                MetricLabel.OPERATION: operation,
+                MetricLabel.PAYLOAD_TYPE: payload_type,
+            },
+        )
+
+    def increment_workflow_start_error(
+        self, workflow_type: str, exception: Exception
+    ) -> None:
+        self.__increment_counter(
+            name=MetricName.WORKFLOW_START_ERROR,
+            documentation=MetricDocumentation.WORKFLOW_START_ERROR,
+            labels={
+                MetricLabel.WORKFLOW_TYPE: workflow_type,
+                MetricLabel.EXCEPTION: str(exception),
+            },
+        )
+
+    def record_workflow_input_payload_size(
+        self, workflow_type: str, version: str, payload_size: int
+    ) -> None:
+        self.__record_gauge(
+            name=MetricName.WORKFLOW_INPUT_SIZE,
+            documentation=MetricDocumentation.WORKFLOW_INPUT_SIZE,
+            labels={
+                MetricLabel.WORKFLOW_TYPE: workflow_type,
+                MetricLabel.WORKFLOW_VERSION: version,
+            },
+            value=payload_size,
+        )
+
+    def record_task_result_payload_size(
+        self, task_type: str, payload_size: int
+    ) -> None:
+        self.__record_gauge(
+            name=MetricName.TASK_RESULT_SIZE,
+            documentation=MetricDocumentation.TASK_RESULT_SIZE,
+            labels={MetricLabel.TASK_TYPE: task_type},
+            value=payload_size,
+        )
+
+    def record_task_poll_time(self, task_type: str, time_spent: float) -> None:
+        self.__record_gauge(
+            name=MetricName.TASK_POLL_TIME,
+            documentation=MetricDocumentation.TASK_POLL_TIME,
+            labels={MetricLabel.TASK_TYPE: task_type},
+            value=time_spent,
+        )
+
+    def record_task_execute_time(self, task_type: str, time_spent: float) -> None:
+        self.__record_gauge(
+            name=MetricName.TASK_EXECUTE_TIME,
+            documentation=MetricDocumentation.TASK_EXECUTE_TIME,
+            labels={MetricLabel.TASK_TYPE: task_type},
+            value=time_spent,
+        )
+
+    def __increment_counter(
+        self,
+        name: MetricName,
+        documentation: MetricDocumentation,
+        labels: Dict[MetricLabel, str],
+    ) -> None:
+        if not self.must_collect_metrics:
+            return
+        counter = self.__get_counter(
+            name=name, documentation=documentation, labelnames=labels.keys()
+        )
+        counter.labels(*labels.values()).inc()
+
+    def __record_gauge(
+        self,
+        name: MetricName,
+        documentation: MetricDocumentation,
+        labels: Dict[MetricLabel, str],
+        value: Any,
+    ) -> None:
+        if not self.must_collect_metrics:
+            return
+        gauge = self.__get_gauge(
+            name=name, documentation=documentation, labelnames=labels.keys()
+        )
+        gauge.labels(*labels.values()).set(value)
+
+    def __get_counter(
+        self,
+        name: MetricName,
+        documentation: MetricDocumentation,
+        labelnames: List[MetricLabel],
+    ) -> Counter:
+        if name not in self.counters:
+            self.counters[name] = self.__generate_counter(
+                name, documentation, labelnames
+            )
+        return self.counters[name]
+
+    def __get_gauge(
+        self,
+        name: MetricName,
+        documentation: MetricDocumentation,
+        labelnames: List[MetricLabel],
+    ) -> Gauge:
+        if name not in self.gauges:
+            self.gauges[name] = self.__generate_gauge(name, documentation, labelnames)
+        return self.gauges[name]
+
+    def __generate_counter(
+        self,
+        name: MetricName,
+        documentation: MetricDocumentation,
+        labelnames: List[MetricLabel],
+    ) -> Counter:
+        return Counter(
+            name=name,
+            documentation=documentation,
+            labelnames=labelnames,
+            registry=self.registry,
+        )
+
+    def __generate_gauge(
+        self,
+        name: MetricName,
+        documentation: MetricDocumentation,
+        labelnames: List[MetricLabel],
+    ) -> Gauge:
+        return Gauge(
+            name=name,
+            documentation=documentation,
+            labelnames=labelnames,
+            registry=self.registry,
+        )
diff --git a/omagent_core/engine/telemetry/model/__init__.py b/omagent_core/engine/telemetry/model/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/telemetry/model/metric_documentation.py b/omagent_core/engine/telemetry/model/metric_documentation.py
new file mode 100644
index 0000000000000000000000000000000000000000..9f63f5d5d3915e5f5b99241512b07822353b85de
--- /dev/null
+++ b/omagent_core/engine/telemetry/model/metric_documentation.py
@@ -0,0 +1,19 @@
+from enum import Enum
+
+
+class MetricDocumentation(str, Enum):
+    EXTERNAL_PAYLOAD_USED = "Incremented each time external payload storage is used"
+    TASK_ACK_ERROR = "Task ack has encountered an exception"
+    TASK_ACK_FAILED = "Task ack failed"
+    TASK_EXECUTE_ERROR = "Execution error"
+    TASK_EXECUTE_TIME = "Time to execute a task"
+    TASK_EXECUTION_QUEUE_FULL = "Counter to record execution queue has saturated"
+    TASK_PAUSED = "Counter for number of times the task has been polled, when the worker has been paused"
+    TASK_POLL = "Incremented each time polling is done"
+    TASK_POLL_ERROR = "Client error when polling for a task queue"
+    TASK_POLL_TIME = "Time to poll for a batch of tasks"
+    TASK_RESULT_SIZE = "Records output payload size of a task"
+    TASK_UPDATE_ERROR = "Task status cannot be updated back to server"
+    THREAD_UNCAUGHT_EXCEPTION = "thread_uncaught_exceptions"
+    WORKFLOW_START_ERROR = "Counter for workflow start errors"
+    WORKFLOW_INPUT_SIZE = "Records input payload size of a workflow"
diff --git a/omagent_core/engine/telemetry/model/metric_label.py b/omagent_core/engine/telemetry/model/metric_label.py
new file mode 100644
index 0000000000000000000000000000000000000000..149924843405ce9a45baec78fdd368ea55e7468c
--- /dev/null
+++ b/omagent_core/engine/telemetry/model/metric_label.py
@@ -0,0 +1,11 @@
+from enum import Enum
+
+
+class MetricLabel(str, Enum):
+    ENTITY_NAME = "entityName"
+    EXCEPTION = "exception"
+    OPERATION = "operation"
+    PAYLOAD_TYPE = "payload_type"
+    TASK_TYPE = "taskType"
+    WORKFLOW_TYPE = "workflowType"
+    WORKFLOW_VERSION = "version"
diff --git a/omagent_core/engine/telemetry/model/metric_name.py b/omagent_core/engine/telemetry/model/metric_name.py
new file mode 100644
index 0000000000000000000000000000000000000000..1301434b5c7520c8845d5945786fbd32abcda7f8
--- /dev/null
+++ b/omagent_core/engine/telemetry/model/metric_name.py
@@ -0,0 +1,19 @@
+from enum import Enum
+
+
+class MetricName(str, Enum):
+    EXTERNAL_PAYLOAD_USED = "external_payload_used"
+    TASK_ACK_ERROR = "task_ack_error"
+    TASK_ACK_FAILED = "task_ack_failed"
+    TASK_EXECUTE_ERROR = "task_execute_error"
+    TASK_EXECUTE_TIME = "task_execute_time"
+    TASK_EXECUTION_QUEUE_FULL = "task_execution_queue_full"
+    TASK_PAUSED = "task_paused"
+    TASK_POLL = "task_poll"
+    TASK_POLL_ERROR = "task_poll_error"
+    TASK_POLL_TIME = "task_poll_time"
+    TASK_RESULT_SIZE = "task_result_size"
+    TASK_UPDATE_ERROR = "task_update_error"
+    THREAD_UNCAUGHT_EXCEPTION = "thread_uncaught_exceptions"
+    WORKFLOW_INPUT_SIZE = "workflow_input_size"
+    WORKFLOW_START_ERROR = "workflow_start_error"
diff --git a/omagent_core/engine/worker/__init__.py b/omagent_core/engine/worker/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/worker/base.py b/omagent_core/engine/worker/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..e88c76e30f40b494c1fc85355d9d35d63794fefe
--- /dev/null
+++ b/omagent_core/engine/worker/base.py
@@ -0,0 +1,234 @@
+import asyncio
+import dataclasses
+import inspect
+import logging
+import socket
+import time
+import traceback
+from abc import ABC, abstractmethod
+from copy import deepcopy
+from typing import Any, Callable, Optional, Union
+
+from omagent_core.base import BotBase
+from omagent_core.engine.automator import utils
+from omagent_core.engine.automator.utils import convert_from_dict_or_list
+from omagent_core.engine.configuration.configuration import Configuration
+from omagent_core.engine.http.api_client import ApiClient
+from omagent_core.engine.http.models import TaskExecLog
+from omagent_core.engine.http.models.task import Task
+from omagent_core.engine.http.models.task_result import TaskResult
+from omagent_core.engine.http.models.task_result_status import TaskResultStatus
+from omagent_core.engine.worker.exception import NonRetryableException
+from pydantic import Field
+from typing_extensions import Self
+
+ExecuteTaskFunction = Callable[[Union[Task, object]], Union[TaskResult, object]]
+
+logger = logging.getLogger(Configuration.get_logging_formatted_name(__name__))
+
+
+def is_callable_input_parameter_a_task(
+    callable: ExecuteTaskFunction, object_type: Any
+) -> bool:
+    parameters = inspect.signature(callable).parameters
+    if len(parameters) != 1:
+        return False
+    parameter = parameters[list(parameters.keys())[0]]
+    return (
+        parameter.annotation == object_type
+        or parameter.annotation == parameter.empty
+        or parameter.annotation == object
+    )
+
+
+def is_callable_return_value_of_type(
+    callable: ExecuteTaskFunction, object_type: Any
+) -> bool:
+    return_annotation = inspect.signature(callable).return_annotation
+    return return_annotation == object_type
+
+
+class BaseWorker(BotBase, ABC):
+    poll_interval: float = Field(
+        default=100, description="Worker poll interval in millisecond"
+    )
+    domain: Optional[str] = Field(default=None, description="The domain of workflow")
+    concurrency: int = Field(default=5, description="The concurrency of worker")
+    _task_type: Optional[str] = None
+
+    def model_post_init(self, __context: Any) -> None:
+        self.task_definition_name = self.id or self.name
+        self.next_task_index = 0
+        self._task_definition_name_cache = None
+
+        self.api_client = ApiClient()
+        self.worker_id = deepcopy(self.get_identity())
+
+        self._workflow_instance_id = None
+        self._task_type = None
+        for _, attr_value in self.__dict__.items():
+            if isinstance(attr_value, BotBase):
+                attr_value._parent = self
+
+    @property
+    def task_type(self) -> str:
+        return self._task_type
+
+    @task_type.setter
+    def task_type(self, value: str):
+        self._task_type = value
+
+    @property
+    def workflow_instance_id(self) -> Optional[str]:
+        return self._workflow_instance_id
+
+    @workflow_instance_id.setter
+    def workflow_instance_id(self, value: Optional[str]):
+        self._workflow_instance_id = value
+
+    @abstractmethod
+    def _run(self, *args, **kwargs) -> Any:
+        """Run the Node."""
+
+    def __call__(self, *args: Any, **kwds: Any) -> Any:
+        print ("__call__")
+        return self._run(*args, **kwds)
+
+    def execute(self, task: Task) -> TaskResult:
+        task_input = {}
+        task_output = None
+        task_result: TaskResult = self.get_task_result_from_task(task)
+        if task.conversation_info:
+            self.workflow_instance_id = '|'.join([
+                task.workflow_instance_id,
+                task.conversation_info.get('agentId', ''),
+                task.conversation_info.get('conversationId', ''),
+                task.conversation_info.get('chatId', ''),
+            ])
+        else:
+            self.workflow_instance_id = task.workflow_instance_id
+            
+        try:
+            if is_callable_input_parameter_a_task(
+                callable=self._run,
+                object_type=Task,
+            ):
+                task_output = self._run(task)
+            else:
+                params = inspect.signature(self._run).parameters
+                for input_name in params:
+                    typ = params[input_name].annotation
+                    default_value = params[input_name].default
+                    if input_name in task.input_data:
+                        if typ in utils.simple_types:
+                            task_input[input_name] = task.input_data[input_name]
+                        else:
+                            task_input[input_name] = convert_from_dict_or_list(
+                                typ, task.input_data[input_name]
+                            )
+                    else:
+                        if default_value is not inspect.Parameter.empty:
+                            task_input[input_name] = default_value
+                        else:
+                            task_input[input_name] = None
+                if inspect.iscoroutinefunction(self._run):
+                    try:
+                        loop = asyncio.get_running_loop()
+                    except RuntimeError:
+                        loop = asyncio.new_event_loop()
+                        asyncio.set_event_loop(loop)
+
+                    task_output = loop.run_until_complete(
+                        asyncio.gather(self._run(**task_input), return_exceptions=True)
+                    )[0]
+                else:
+                    task_output = self._run(**task_input)
+            if type(task_output) == TaskResult:
+                task_output.task_id = task.task_id
+                task_output.workflow_instance_id = task.workflow_instance_id
+                return task_output
+            else:
+                task_result.status = TaskResultStatus.COMPLETED
+                task_result.output_data = task_output
+
+        except NonRetryableException as ne:
+            task_result.status = TaskResultStatus.FAILED_WITH_TERMINAL_ERROR
+            if len(ne.args) > 0:
+                task_result.reason_for_incompletion = ne.args[0]
+
+        except Exception as ne:
+            logger.error(
+                f"Error executing task {task.task_def_name} with id {task.task_id}.  error = {traceback.format_exc()}"
+            )
+
+            task_result.logs = [
+                TaskExecLog(
+                    traceback.format_exc(), task_result.task_id, int(time.time())
+                )
+            ]
+            task_result.status = TaskResultStatus.FAILED
+            if len(ne.args) > 0:
+                task_result.reason_for_incompletion = ne.args[0]
+        self.workflow_instance_id = None
+
+        if dataclasses.is_dataclass(type(task_result.output_data)):
+            task_output = dataclasses.asdict(task_result.output_data)
+            task_result.output_data = task_output
+            return task_result
+        if not isinstance(task_result.output_data, dict):
+            task_output = task_result.output_data
+            task_result.output_data = self.api_client.sanitize_for_serialization(
+                task_output
+            )
+            if not isinstance(task_result.output_data, dict):
+                task_result.output_data = {"result": task_result.output_data}
+        return task_result
+
+    def get_identity(self) -> str:
+        return self.worker_id if hasattr(self, "worker_id") else socket.gethostname()
+
+    def get_polling_interval_in_seconds(self) -> float:
+        return self.poll_interval / 1000
+
+    def get_task_definition_name(self) -> str:
+        return self.task_definition_name_cache
+
+    @property
+    def task_definition_names(self):
+        if isinstance(self.task_definition_name, list):
+            return self.task_definition_name
+        else:
+            return [self.task_definition_name]
+
+    @property
+    def task_definition_name_cache(self):
+        if self._task_definition_name_cache is None:
+            self._task_definition_name_cache = self.compute_task_definition_name()
+        return self._task_definition_name_cache
+
+    def clear_task_definition_name_cache(self):
+        self._task_definition_name_cache = None
+
+    def compute_task_definition_name(self):
+        if isinstance(self.task_definition_name, list):
+            task_definition_name = self.task_definition_name[self.next_task_index]
+            self.next_task_index = (self.next_task_index + 1) % len(
+                self.task_definition_name
+            )
+            return task_definition_name
+        return self.task_definition_name
+
+    def get_task_result_from_task(self, task: Task) -> TaskResult:
+        return TaskResult(
+            task_id=task.task_id,
+            workflow_instance_id=task.workflow_instance_id,
+            worker_id=self.get_identity(),
+            biz_meta=task.biz_meta,
+            callback_url=task.callback_url,
+        )
+
+    def get_domain(self) -> str:
+        return self.domain
+
+    def paused(self) -> bool:
+        return False
diff --git a/omagent_core/engine/worker/exception.py b/omagent_core/engine/worker/exception.py
new file mode 100644
index 0000000000000000000000000000000000000000..82dbf6b360c02bd2444485fcf4f4285bcd95af8f
--- /dev/null
+++ b/omagent_core/engine/worker/exception.py
@@ -0,0 +1,4 @@
+class NonRetryableException(Exception):
+
+    def __init__(self, *args: object) -> None:
+        super().__init__(*args)
diff --git a/omagent_core/engine/workflow/__init__.py b/omagent_core/engine/workflow/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/workflow/conductor_workflow.py b/omagent_core/engine/workflow/conductor_workflow.py
new file mode 100644
index 0000000000000000000000000000000000000000..a476e1c7803f3a5706c75e086cee98b7e529920e
--- /dev/null
+++ b/omagent_core/engine/workflow/conductor_workflow.py
@@ -0,0 +1,514 @@
+import itertools
+from copy import deepcopy
+from typing import Any, Dict, List, Union
+
+from omagent_core.engine.http.models import *
+from omagent_core.engine.workflow.executor.local_workflow_executor import WorkflowExecutor as LiteWorkflowExecutor
+from omagent_core.engine.http.models.start_workflow_request import \
+    IdempotencyStrategy
+from omagent_core.engine.orkes.orkes_workflow_client import workflow_client
+from omagent_core.engine.workflow.executor.workflow_executor import \
+    WorkflowExecutor
+from omagent_core.engine.workflow.task.fork_task import ForkTask
+from omagent_core.engine.workflow.task.join_task import JoinTask
+from omagent_core.engine.workflow.task.set_variable_task import SetVariableTask
+from omagent_core.engine.workflow.task.switch_task import SwitchTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from omagent_core.engine.workflow.task.timeout_policy import TimeoutPolicy
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+from shortuuid import uuid
+from typing_extensions import Self
+import os
+from omagent_core.engine.http.models.workflow_def import to_workflow_def
+from omagent_core.engine.workflow.task.simple_task import simple_task
+
+class ConductorWorkflow:
+    SCHEMA_VERSION = 2
+
+    def __init__(self, name: str, version: int = None, description: str = None, lite_version: bool = False) -> Self:
+        if lite_version or os.getenv("OMAGENT_MODE") == "lite":
+            self._executor = LiteWorkflowExecutor()
+        else:
+            self._executor = WorkflowExecutor()
+        self.name = name
+        self.version = version
+        self.description = description
+        self.lite_version = lite_version
+        self._tasks = []
+        self._owner_email = "default@omagent.ai"
+        self._timeout_policy = None
+        self._timeout_seconds = 60
+        self._failure_workflow = ""
+        self._input_parameters = []
+        self._output_parameters = {}
+        self._input_template = {}
+        self._variables = {}
+        self._restartable = True
+        self._workflow_status_listener_enabled = False
+        self._workflow_status_listener_sink = None
+        if container.conductor_config.debug:
+            self.stop_all_running_workflows()
+
+    def initialization(self, worker_config):
+        for config in worker_config:
+            worker_cls = registry.get_worker(config['name'])
+            self.workers[config['name']] = worker_cls(**config)
+        return self.workers
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @name.setter
+    def name(self, name: str) -> None:
+        if not isinstance(name, str):
+            raise Exception("invalid type")
+        self._name = deepcopy(name)
+
+    @property
+    def version(self) -> int:
+        return self._version
+
+    @version.setter
+    def version(self, version: int) -> None:
+        if version != None and not isinstance(version, int):
+            raise Exception("invalid type")
+        self._version = deepcopy(version)
+
+    @property
+    def description(self) -> str:
+        return self._description
+
+    @description.setter
+    def description(self, description: str) -> None:
+        if description != None and not isinstance(description, str):
+            raise Exception("invalid type")
+        self._description = deepcopy(description)
+
+    def timeout_policy(self, timeout_policy: TimeoutPolicy) -> Self:
+        if not isinstance(timeout_policy, TimeoutPolicy):
+            raise Exception("invalid type")
+        self._timeout_policy = deepcopy(timeout_policy)
+        return self
+
+    def timeout_seconds(self, timeout_seconds: int) -> Self:
+        if not isinstance(timeout_seconds, int):
+            raise Exception("invalid type")
+        self._timeout_seconds = deepcopy(timeout_seconds)
+        return self
+
+    def owner_email(self, owner_email: str) -> Self:
+        if not isinstance(owner_email, str):
+            raise Exception("invalid type")
+        self._owner_email = deepcopy(owner_email)
+        return self
+
+    # Name of the workflow to execute when this workflow fails.
+    # Failure workflows can be used for handling compensation logic
+    def failure_workflow(self, failure_workflow: str) -> Self:
+        if not isinstance(failure_workflow, str):
+            raise Exception("invalid type")
+        self._failure_workflow = deepcopy(failure_workflow)
+        return self
+
+    # If the workflow can be restarted after it has reached terminal state.
+    # Set this to false if restarting workflow can have side effects
+    def restartable(self, restartable: bool) -> Self:
+        if not isinstance(restartable, bool):
+            raise Exception("invalid type")
+        self._restartable = deepcopy(restartable)
+        return self
+
+    def enable_status_listener(self, sink_name: bool) -> Self:
+        self._workflow_status_listener_sink = sink_name
+        self._workflow_status_listener_enabled = True
+
+    def disable_status_listener(self) -> Self:
+        self._workflow_status_listener_sink = None
+        self._workflow_status_listener_enabled = False
+
+    # Workflow output follows similar structure as task input
+    # See https://conductor.netflix.com/how-tos/Tasks/task-inputs.html for more details
+    def output_parameters(self, output_parameters: Dict[str, Any]) -> Self:
+        if output_parameters == None:
+            self._output_parameters = {}
+            return
+        if not isinstance(output_parameters, dict):
+            raise Exception("invalid type")
+        for key in output_parameters.keys():
+            if not isinstance(key, str):
+                raise Exception("invalid type")
+        self._output_parameters = deepcopy(output_parameters)
+        return self
+
+    def output_parameter(self, key: str, value: Any) -> Self:
+        if self._output_parameters is None:
+            self._output_parameters = {}
+
+        self._output_parameters[key] = value
+        return self
+
+    # InputTemplate template input to the workflow.  Can have combination of variables (e.g. ${workflow.input.abc}) and static values
+    def input_template(self, input_template: Dict[str, Any]) -> Self:
+        if input_template == None:
+            self._input_template = {}
+            return
+        if not isinstance(input_template, dict):
+            raise Exception("invalid type")
+        for key in input_template.keys():
+            if not isinstance(key, str):
+                raise Exception("invalid type")
+        self._input_template = deepcopy(input_template)
+        return self
+
+    # Variables are set using SET_VARIABLE task. Excellent way to maintain business state
+    # e.g. Variables can maintain business/user specific states which can be queried and inspected to find out the state of the workflow
+    def variables(self, variables: Dict[str, Any]) -> Self:
+        if variables == None:
+            self._variables = {}
+            return
+        if not isinstance(variables, dict):
+            raise Exception("invalid type")
+        for key in variables.keys():
+            if not isinstance(key, str):
+                raise Exception("invalid type")
+        self._variables = deepcopy(variables)
+        return self
+
+    # List of the input parameters to the workflow. Usage: documentation ONLY
+    def input_parameters(self, input_parameters: List[str]) -> Self:
+        if isinstance(input_parameters, dict) or isinstance(input_parameters, Dict):
+            self._input_template = input_parameters
+            return self
+        if not isinstance(input_parameters, list):
+            raise Exception("invalid type")
+        for input_parameter in input_parameters:
+            if not isinstance(input_parameter, str):
+                raise Exception("invalid type")
+        self._input_parameters = deepcopy(input_parameters)
+        return self
+
+    def workflow_input(self, input: dict) -> Self:
+        keys = list(input.keys())
+        self.input_template(input)
+        return self
+        
+    def load(self, json_file_path: str) -> None:
+        import json
+        from pathlib import Path
+
+        # Load the JSON file
+        json_path = Path(json_file_path)
+        if not json_path.is_file():
+            raise FileNotFoundError(f"The file {json_file_path} does not exist.")
+
+        with open(json_path, 'r') as file:
+            data = json.load(file)
+        workflow_def = to_workflow_def(json_data=data)
+        self.name = workflow_def.name
+        self._tasks = [simple_task(task_def_name=task.name, task_reference_name=task.task_reference_name, inputs=task.input_parameters) for task in workflow_def.tasks]
+        self._input_parameters = workflow_def.input_parameters
+        self._output_parameters = workflow_def.output_parameters
+        self._failure_workflow = workflow_def.failure_workflow
+        self._timeout_seconds = workflow_def.timeout_seconds
+        self._variables = workflow_def.variables
+        self._input_template = workflow_def.input_template
+        self._workflow_status_listener_enabled = workflow_def.workflow_status_listener_enabled
+        self._owner_email = workflow_def.owner_email
+
+    
+
+    # Register the workflow definition with the server. If overwrite is set, the definition on the server will be
+    # overwritten. When not set, the call fails if there is any change in the workflow definition between the server
+    # and what is being registered.
+    def register(self, overwrite: bool):
+        return self._executor.register_workflow(
+            overwrite=overwrite,
+            workflow=self.to_workflow_def(),
+        )
+
+    def start_workflow(self, start_workflow_request: StartWorkflowRequest) -> str:
+        """
+        Executes the workflow inline without registering with the server.  Useful for one-off workflows that need not be registered.
+        Parameters
+        ----------
+        start_workflow_request
+
+        Returns
+        -------
+        Workflow Execution Id
+        """
+        start_workflow_request.workflow_def = self.to_workflow_def()
+        start_workflow_request.name = self.name
+        start_workflow_request.version = self.version
+        return self._executor.start_workflow(start_workflow_request)
+
+    def start_workflow_with_input(
+        self,
+        workflow_input: dict = {},
+        correlation_id=None,
+        task_to_domain=None,
+        priority=None,
+        idempotency_key: str = None,
+        idempotency_strategy: IdempotencyStrategy = IdempotencyStrategy.FAIL, workers=None
+    ) -> str:
+        """
+        Starts the workflow with given inputs and parameters and returns the id of the started workflow
+        """
+
+        start_workflow_request = StartWorkflowRequest()
+        start_workflow_request.workflow_def = self.to_workflow_def()
+        start_workflow_request.name = self.name
+        start_workflow_request.version = self.version
+        start_workflow_request.input = workflow_input
+        start_workflow_request.correlation_id = correlation_id
+        start_workflow_request.idempotency_key = idempotency_key
+        start_workflow_request.idempotency_strategy = idempotency_strategy
+        start_workflow_request.priority = priority
+        start_workflow_request.task_to_domain = task_to_domain
+        
+        return self._executor.start_workflow(start_workflow_request, workers)
+    
+
+    def get_workflow(self, workflow_id: str, include_tasks: bool = None) -> Workflow:
+        return self._executor.get_workflow(workflow_id, include_tasks)
+
+    def execute(
+        self,
+        workflow_input: Any = {},
+        wait_until_task_ref: str = "",
+        wait_for_seconds: int = 10,
+        request_id: str = None,
+        idempotency_key: str = None,
+        idempotency_strategy: IdempotencyStrategy = IdempotencyStrategy.FAIL,
+        task_to_domain: dict[str, str] = None,
+    ) -> WorkflowRun:
+        """
+        Executes a workflow synchronously.  Useful for short duration workflow (e.g. < 20 seconds)
+        Parameters
+        ----------
+        workflow_input Input to the workflow
+        wait_until_task_ref wait reference name of the task to wait until before returning the workflow results
+        wait_for_seconds amount of time to wait in seconds before returning.
+        request_id User supplied unique id that represents this workflow run
+        Returns
+        -------
+        Workflow execution run.  check the status field to identify if the workflow was completed or still running
+        when the call completed.
+        """
+        request = StartWorkflowRequest()
+        request.workflow_def = self.to_workflow_def()
+        request.input = workflow_input
+        request.name = request.workflow_def.name
+        request.version = 1
+        if idempotency_key is not None:
+            request.idempotency_key = idempotency_key
+            request.idempotency_strategy = idempotency_strategy
+        if task_to_domain is not None:
+            request.task_to_domain = task_to_domain
+        run = self._executor.execute_workflow(
+            request,
+            wait_until_task_ref=wait_until_task_ref,
+            wait_for_seconds=wait_for_seconds,
+            request_id=request_id,
+        )
+
+        return run
+
+    def to_workflow_def(self) -> WorkflowDef:
+        return WorkflowDef(
+            name=self._name,
+            description=self._description,
+            version=self._version,
+            tasks=self.__get_workflow_task_list(),
+            input_parameters=self._input_parameters,
+            output_parameters=self._output_parameters,
+            failure_workflow=self._failure_workflow,
+            schema_version=ConductorWorkflow.SCHEMA_VERSION,
+            owner_email=self._owner_email,
+            timeout_policy=self._timeout_policy,
+            timeout_seconds=self._timeout_seconds,
+            variables=self._variables,
+            input_template=self._input_template,
+            workflow_status_listener_enabled=self._workflow_status_listener_enabled,
+            workflow_status_listener_sink=self._workflow_status_listener_sink,
+        )
+
+    def to_workflow_task(self):
+        sub_workflow_task = InlineSubWorkflowTask(
+            task_ref_name=self.name + "_" + str(uuid()), workflow=self
+        )
+        sub_workflow_task.input_parameters.update(self._input_template)
+        return sub_workflow_task.to_workflow_task()
+
+    def __get_workflow_task_list(self) -> List[WorkflowTask]:
+        workflow_task_list = []
+        for task in self._tasks:
+            print (type(task))
+            converted_task = task.to_workflow_task()
+            print (converted_task)
+            if isinstance(converted_task, list):
+                for subtask in converted_task:
+                    workflow_task_list.append(subtask)
+            else:
+                workflow_task_list.append(converted_task)
+        updated_task_list = []
+        for i in range(len(workflow_task_list)):
+            wft: WorkflowTask = workflow_task_list[i]
+            if wft.task_definition is None:
+                wft.task_definition = TaskDef()
+            if container.conductor_config.debug:
+                wft.task_definition.retry_count = 0
+            updated_task_list.append(wft)
+            if (
+                wft.type == "FORK_JOIN"
+                and i < len(workflow_task_list) - 1
+                and workflow_task_list[i + 1].type != "JOIN"
+            ):
+                join_on = list(
+                    map(lambda ft: ft[len(ft) - 1].task_reference_name, wft.fork_tasks)
+                )
+                join = JoinTask(
+                    task_ref_name="join_" + wft.task_reference_name, join_on=join_on
+                )
+                updated_task_list.append(join.to_workflow_task())
+
+        return updated_task_list
+
+    def __rshift__(
+        self, task: Union[TaskInterface, List[TaskInterface], Dict[Any, TaskInterface]]
+    ) -> Self:
+        if isinstance(task, list):
+            forked_tasks = []
+            for fork_task in task:
+                if isinstance(fork_task, list):
+                    forked_tasks.append(fork_task)
+                else:
+                    forked_tasks.append([fork_task])
+            self.__add_fork_join_tasks(forked_tasks)
+            return self
+        elif isinstance(task, dict):
+            switch_task = SwitchTask(
+                task_ref_name="switch",
+                case_expression=self._tasks[-1].output("switch_case_value"),
+            )
+            if "default" in task:
+                switch_task.default_case(task.pop("default"))
+            for key, value in task.items():
+                switch_task.switch_case(key, value)
+
+            return self.__add_task(switch_task)
+
+        elif isinstance(task, ConductorWorkflow):
+            # inline = InlineSubWorkflowTask(
+            #     task_ref_name=task.name + "_" + str(uuid()), workflow=task
+            # )
+            # inline.input_parameters.update(task._input_template)
+            # return self.__add_task(inline)
+            sub_workflow_tasks = task._tasks
+            for sub_task in sub_workflow_tasks:
+                self.__add_task(sub_task)
+            return self
+
+        elif isinstance(task, TaskInterface):
+            return self.__add_task(task)
+
+        else:
+            raise ValueError(f"Invalid task type {type(task)}")
+
+    # Append task
+    def add(self, task: Union[TaskInterface, List[TaskInterface]]) -> Self:
+        if isinstance(task, list):
+            for t in task:
+                self.__add_task(t)
+            return self
+        return self.__add_task(task)
+
+    def __add_task(self, task: TaskInterface) -> Self:
+        if not (
+            issubclass(type(task), TaskInterface) or isinstance(task, ConductorWorkflow)
+        ):
+            raise Exception(
+                f"invalid task -- if using @worker_task or @WorkerTask decorator ensure task_ref_name is passed as "
+                f"argument.  task is {type(task)}"
+            )
+        self._tasks.append(deepcopy(task))
+        return self
+
+    def __add_fork_join_tasks(self, forked_tasks: List[List[TaskInterface]]) -> Self:
+        for single_fork in forked_tasks:
+            for task in single_fork:
+                if not (
+                    issubclass(type(task), TaskInterface)
+                    or isinstance(task, ConductorWorkflow)
+                ):
+                    raise Exception("invalid type")
+
+        suffix = str(uuid())
+
+        fork_task = ForkTask(
+            task_ref_name="forked_" + suffix,
+            forked_tasks=forked_tasks,
+            join_on=[
+                each.task_reference_name for each in itertools.chain(*forked_tasks)
+            ],
+        )
+        self._tasks.append(fork_task)
+        return self
+
+    def __call__(self, **kwargs) -> WorkflowRun:
+        input = {}
+        if kwargs is not None and len(kwargs) > 0:
+            input = kwargs
+        return self.execute(workflow_input=input)
+
+    def input(self, json_path: str) -> str:
+        if json_path is None:
+            return "${" + f"workflow.input" + "}"
+        else:
+            return "${" + f"workflow.input.{json_path}" + "}"
+
+    def output(self, json_path: str = None) -> str:
+        if json_path is None:
+            return "${" + f"workflow.output" + "}"
+        else:
+            return "${" + f"workflow.output.{json_path}" + "}"
+
+    def stop_all_running_workflows(self):
+        try:
+            running_workflows = workflow_client.search(query="status IN (RUNNING)")
+            if running_workflows:
+                for workflow in running_workflows.results:
+                    if workflow.workflow_type == self.name:
+                        workflow_client.terminate_workflow(
+                            workflow_id=workflow.workflow_id
+                        )
+                logging.info("Stopped all running workflows")
+            else:
+                logging.info("No running workflows found")
+        except Exception as e:
+            logging.error(f"Error while stopping running workflows: {e}")
+
+
+class InlineSubWorkflowTask(TaskInterface):
+    def __init__(self, task_ref_name: str, workflow: ConductorWorkflow) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.SUB_WORKFLOW,
+        )
+        self._workflow = deepcopy(workflow)
+        self._workflow_name = deepcopy(workflow.name)
+        self._workflow_version = deepcopy(workflow.version)
+        self._workflow_definition = deepcopy(workflow.to_workflow_def())
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow = super().to_workflow_task()
+        workflow.sub_workflow_param = SubWorkflowParams(
+            name=self._workflow_name,
+            version=self._workflow_version,
+            workflow_definition=self._workflow_definition,
+        )
+        return workflow
diff --git a/omagent_core/engine/workflow/executor/__init__.py b/omagent_core/engine/workflow/executor/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/workflow/executor/local_workflow_executor.py b/omagent_core/engine/workflow/executor/local_workflow_executor.py
new file mode 100644
index 0000000000000000000000000000000000000000..fabf0ed800445ec4a6d0701acec772b47aa5fea2
--- /dev/null
+++ b/omagent_core/engine/workflow/executor/local_workflow_executor.py
@@ -0,0 +1,263 @@
+from typing import Dict
+import logging
+from omagent_core.utils.registry import registry
+import logging
+from omagent_core.engine.http.models import *
+import json
+import inspect
+
+
+class LocalWorkflowExecutor:
+    def __init__(self):
+        logging.basicConfig(level=logging.INFO)
+        self.workers = {}
+        self.task_outputs = {}
+        self.workflow_variables = {}
+        
+    def evaluate_input_parameters(self, task: Dict) -> Dict:
+        processed_inputs = {}
+        input_params = task.get('inputParameters') or task.get('input_parameters', {})
+
+        for key, value in input_params.items():
+            if isinstance(value, str) and value.startswith('${') and value.endswith('}'):
+                ref_path = value[2:-1]
+                parts = ref_path.split('.')
+                
+                if parts[0] in self.task_outputs:
+                    task_output = self.task_outputs[parts[0]][parts[1]]
+                    for part in parts[2:]:  
+                        if isinstance(task_output, dict):
+                            task_output = task_output.get(part, {})
+                    processed_inputs[key] = task_output
+            else:
+                processed_inputs[key] = value
+        return processed_inputs
+
+
+    def start_workflow(self, workflow_def, start_request, workers) -> str:
+        print ("start_request:", start_request.input)
+        self.task_outputs["workflow"] = {"input": start_request.input}        
+        output = {}
+        for i, task_def in enumerate(workflow_def.tasks):
+            if i == 0:
+                task_def.input_parameters = start_request.input
+            output = self.execute_task(task_def.to_dict(), workers)
+        return output
+
+        #return workflow_id
+    def worker_task(self, worker, *args, **kwargs):
+        """Run the worker and put its output in the queue."""
+        result = worker._run(*args, **kwargs)
+        self.worker_output_queue.put(result)
+
+    def execute_task(self, task: Dict, workers) -> Dict:
+        """Execute a single task"""
+        task_name = task['name']
+        task_type = task['type']
+        
+        if task_type == 'SIMPLE':
+            worker = workers[task_name]
+            """
+            if not worker_class:
+                raise ValueError(f"Worker {task_name} not found")
+            
+            worker = worker_class()            
+            """
+            inputs = self.evaluate_input_parameters(task)
+            print(inputs)
+            signature = inspect.signature(worker._run)
+            signature_params = dict(signature.parameters)
+            checked_inputs = {}
+            for signature_param_key, signature_param_value in signature_params.items():
+                if signature_param_key in ['args', 'kwargs']:
+                    continue
+                if signature_param_key not in inputs:
+                    checked_inputs[signature_param_key] = None
+                else:
+                    checked_inputs[signature_param_key] = inputs[signature_param_key]
+            # Execute task
+            result = worker._run(**checked_inputs)
+            # Store output
+            task_ref_key = 'taskReferenceName' if 'taskReferenceName' in task else 'task_reference_name'
+
+            self.task_outputs[task[task_ref_key]] = {
+                'output': result
+            }            
+            return result
+            
+        elif task_type == 'DO_WHILE':
+            while True:
+                # Execute all tasks in loop
+                for loop_task in task['loopOver' if 'loopOver' in task else 'loop_over']:                    
+                    self.execute_task(loop_task, workers)
+                if 'loopCondition' in task or "loop_condition" in task:
+                    should_continue = self.evaluate_loop_condition(task['loopCondition' if 'loopCondition' in task else "loop_condition"])            
+                    if not should_continue:
+                        break
+                else:
+                    exit_monitor_output = self.task_outputs['task_exit_monitor']['output']
+                    if exit_monitor_output.get('exit_flag', False):
+                        break
+                    
+        elif task_type == 'SWITCH':
+            case_value = self.evaluate_input_parameters(task)['switchCaseValue']
+            if case_value in task['decision_cases']:
+                for case_task in task['decision_cases'][case_value]:
+                    self.execute_task(case_task.to_dict(), workers)
+            else:
+                for default_task in task.get('defaultCase' if 'defaultCase' in task else 'default_case', []):
+                    self.execute_task(default_task.to_dict(), workers)
+                    
+        return self.task_outputs
+
+    def evaluate_loop_condition(self, condition: str) -> bool:
+        """Evaluate loop condition using the task outputs"""
+        # Clean up the condition string
+        condition = condition.strip()
+        if condition.startswith('if'):
+            condition = condition[2:].strip()
+        
+        # Extract the main condition part (between parentheses)
+        main_condition = condition[condition.find('(') + 1:condition.rfind(')')].strip()
+        
+        # First split by OR operator (||)
+        or_conditions = [cond.strip() for cond in main_condition.split('||')]
+        
+        for or_part in or_conditions:
+            # For each OR part, split by AND operator (&&)
+            and_conditions = [cond.strip() for cond in or_part.split('&&')]
+            
+            # All AND conditions must be true for this OR part to be true
+            and_results = True
+            for and_condition in and_conditions:
+                condition_result = self._evaluate_single_condition(and_condition)
+                if not condition_result:
+                    and_results = False
+                    break
+            
+            # If any OR part (all of its AND conditions) is true, return false to stop the loop
+            if and_results:
+                return False
+        
+        # If no OR conditions were true (meaning no AND group was completely true)
+        # return true to continue the loop
+        return True
+
+    def _evaluate_single_condition(self, condition: str) -> bool:
+        """Evaluate a single condition without OR operators"""
+        condition = condition.strip('()')
+        
+        if '$.' not in condition:
+            return False
+            
+        # Handle different comparison operators
+        if '==' in condition:
+            left, right = [part.strip() for part in condition.split('==')]
+            operator = '=='
+        elif '>=' in condition:  # Check >= before > to avoid incorrect splitting
+            left, right = [part.strip() for part in condition.split('>=')]
+            operator = '>='
+        elif '<=' in condition:  # Check <= before < to avoid incorrect splitting
+            left, right = [part.strip() for part in condition.split('<=')]
+            operator = '<='
+        elif '!=' in condition:
+            left, right = [part.strip() for part in condition.split('!=')]
+            operator = '!='
+        elif '>' in condition:
+            left, right = [part.strip() for part in condition.split('>')]
+            operator = '>'
+        elif '<' in condition:
+            left, right = [part.strip() for part in condition.split('<')]
+            operator = '<'
+        else:
+            return False
+            
+        # Parse left side (task reference)    
+        if left.startswith('$.'):
+            task_ref_full = left[2:]  # Remove $.
+            if '[' in task_ref_full:
+                task_ref = task_ref_full.split('[')[0]  # Get part before [
+                array_part = task_ref_full[task_ref_full.find('[')+1:task_ref_full.find(']')].replace("'","").replace('"','')  # Get part between [ ]
+                properties = [array_part]  # Use the array part as a property
+            elif "." in task_ref_full:
+                task_ref,array_part= task_ref_full.split(".")
+                properties = [array_part]  # Use the array part as a property
+            else:
+                task_ref = task_ref_full
+                properties = []
+            
+            # Get task output and navigate through properties
+            value = self.task_outputs.get(task_ref, {}).get('output', {}).get(array_part, {})            
+            #if type(value) == bool:
+            #    return value
+            # Parse right side (could be another task reference or a literal)
+            if right.startswith('$.'):
+                task_ref = right[2:].split('.')[0]
+                properties = right[2:].split('.')[1:]
+                right_value = self.task_outputs.get(task_ref, {}).get('output', {})
+                for prop in properties:
+                    if isinstance(right_value, dict):
+                        right_value = right_value.get(prop)
+                    else:
+                        return False
+            else:
+                # Handle literals
+                if right.lower() == 'true':
+                    right_value = True
+                elif right.lower() == 'false':
+                    right_value = False
+                else:
+                    try:
+                        right_value = int(right)
+                    except ValueError:
+                        right_value = right
+            # Compare values
+            if operator == '>':
+                return value > right_value
+            elif operator == '>=':
+                return value >= right_value
+            elif operator == '<':
+                return value < right_value
+            elif operator == '<=':
+                return value <= right_value
+            elif operator == '!=':
+                return value != right_value
+            else:  # operator == '=='
+                return value == right_value
+                
+        return False
+                
+
+class WorkflowExecutor:
+    def __init__(self):
+        self.local_executor = LocalWorkflowExecutor()    
+        self.status = Workflow(status="RUNNING")
+
+    def start_workflow(self, start_workflow_request: StartWorkflowRequest, workers=None) -> str:
+        try:
+            exe_output = self.local_executor.start_workflow(
+                workflow_def=start_workflow_request.workflow_def,
+                start_request=start_workflow_request, workers=workers
+            )            
+            self.status.status = "COMPLETED"
+            return exe_output
+        except Exception as e:
+            self.status.status = "FAILED"
+            self.status.error = str(e)
+            raise e
+
+
+    def get_workflow(self, workflow_id: str, include_tasks: bool = None) -> Workflow:        
+        return self.status
+    
+
+    def terminate(self, workflow_id: str, reason: str = None):
+        pass
+
+    def register_workflow(self, workflow: WorkflowDef, overwrite: bool = None) -> object:
+        """Create a new workflow definition"""
+        with open(f'{workflow.to_dict()["name"]}.json', 'w') as file:
+            json.dump(workflow.toJSON(), file, indent=4)        
+
+    def __del__(self):
+        pass
\ No newline at end of file
diff --git a/omagent_core/engine/workflow/executor/workflow_executor.py b/omagent_core/engine/workflow/executor/workflow_executor.py
new file mode 100644
index 0000000000000000000000000000000000000000..4ca20a0a0fc62c1032bcf4472433808d659dfaf5
--- /dev/null
+++ b/omagent_core/engine/workflow/executor/workflow_executor.py
@@ -0,0 +1,288 @@
+import uuid
+from typing import Any, Dict, List
+
+from omagent_core.engine.http.api.metadata_resource_api import \
+    MetadataResourceApi
+from omagent_core.engine.http.api.task_resource_api import TaskResourceApi
+from omagent_core.engine.http.api_client import ApiClient
+from omagent_core.engine.http.models import *
+from omagent_core.engine.http.models.correlation_ids_search_request import \
+    CorrelationIdsSearchRequest
+from omagent_core.engine.orkes.orkes_workflow_client import workflow_client
+from omagent_core.utils.container import container
+from typing_extensions import Optional, Self
+
+
+class WorkflowExecutor:
+    def __init__(self) -> Self:
+        self.metadata_client = MetadataResourceApi(
+            ApiClient(container.conductor_config)
+        )
+        self.task_client = TaskResourceApi(ApiClient(container.conductor_config))
+        self.workflow_client = workflow_client
+
+    def register_workflow(
+        self, workflow: WorkflowDef, overwrite: bool = None
+    ) -> object:
+        """Create a new workflow definition"""
+        kwargs = {}
+        if overwrite is not None:
+            kwargs["overwrite"] = overwrite
+        return self.metadata_client.update1(body=[workflow], **kwargs)
+
+    def start_workflow(self, start_workflow_request: StartWorkflowRequest, workers=None) -> str:
+        """Start a new workflow with StartWorkflowRequest, which allows task to be executed in a domain """
+        return self.workflow_client.start_workflow(
+            start_workflow_request=start_workflow_request,
+        )
+
+    def start_workflows(
+        self, *start_workflow_request: StartWorkflowRequest
+    ) -> List[str]:
+        """Start multiple instances of workflows.  Note, there is no parallelism implemented in starting so giving a
+        very large number can impact the latencies and performance
+        """
+        workflow_id_list = [""] * len(start_workflow_request)
+        for i in range(len(start_workflow_request)):
+            workflow_id_list[i] = self.start_workflow(
+                start_workflow_request=start_workflow_request[i]
+            )
+        return workflow_id_list
+
+    def execute_workflow(
+        self,
+        request: StartWorkflowRequest,
+        wait_until_task_ref: str,
+        wait_for_seconds: int = 10,
+        request_id: str = None,
+    ) -> WorkflowRun:
+        """Executes a workflow with StartWorkflowRequest and waits for the completion of the workflow or until a
+        specific task in the workflow"""
+        if request_id is None:
+            request_id = str(uuid.uuid4())
+
+        return self.workflow_client.execute_workflow(
+            start_workflow_request=request,
+            request_id=request_id,
+            wait_until_task_ref=wait_until_task_ref,
+            wait_for_seconds=wait_for_seconds,
+        )
+
+    def execute(
+        self,
+        name: str,
+        version: Optional[int] = None,
+        workflow_input: Any = {},
+        wait_until_task_ref: str = None,
+        wait_for_seconds: int = 10,
+        request_id: str = None,
+        correlation_id: str = None,
+        domain: str = None,
+    ) -> WorkflowRun:
+        """Executes a workflow with StartWorkflowRequest and waits for the completion of the workflow or until a
+        specific task in the workflow"""
+        if request_id is None:
+            request_id = str(uuid.uuid4())
+
+        request = StartWorkflowRequest()
+        request.name = name
+        if version:
+            request.version = version
+        request.input = workflow_input
+        request.correlation_id = correlation_id
+        if domain is not None:
+            request.task_to_domain = {"*": domain}
+
+        return self.workflow_client.execute_workflow(
+            start_workflow_request=request,
+            request_id=request_id,
+            wait_until_task_ref=wait_until_task_ref,
+            wait_for_seconds=wait_for_seconds,
+        )
+
+    def remove_workflow(self, workflow_id: str, archive_workflow: bool = None) -> None:
+        """Removes the workflow permanently from the system"""
+        kwargs = {}
+        if archive_workflow is not None:
+            kwargs["archive_workflow"] = archive_workflow
+        return self.workflow_client.delete_workflow(workflow_id=workflow_id, **kwargs)
+
+    def get_workflow(self, workflow_id: str, include_tasks: bool = None) -> Workflow:
+        """Gets the workflow by workflow id"""
+        kwargs = {}
+        if include_tasks is not None:
+            kwargs["include_tasks"] = include_tasks
+        return self.workflow_client.get_workflow(workflow_id=workflow_id, **kwargs)
+
+    def get_workflow_status(
+        self,
+        workflow_id: str,
+        include_output: bool = None,
+        include_variables: bool = None,
+    ) -> WorkflowStatus:
+        """Gets the workflow by workflow id"""
+        kwargs = {}
+        if include_output is not None:
+            kwargs["include_output"] = include_output
+        if include_variables is not None:
+            kwargs["include_variables"] = include_variables
+        return self.workflow_client.get_workflow_status(
+            workflow_id=workflow_id,
+            include_output=include_output,
+            include_variables=include_variables,
+        )
+
+    def search(
+        self,
+        query_id: str = None,
+        start: int = None,
+        size: int = None,
+        sort: str = None,
+        free_text: str = None,
+        query: str = None,
+        skip_cache: bool = None,
+    ) -> ScrollableSearchResultWorkflowSummary:
+        """Search for workflows based on payload and other parameters"""
+        return self.workflow_client.search(
+            start=start, size=size, free_text=free_text, query=query
+        )
+
+    def get_by_correlation_ids(
+        self,
+        workflow_name: str,
+        correlation_ids: List[str],
+        include_closed: bool = None,
+        include_tasks: bool = None,
+    ) -> dict[str, List[Workflow]]:
+        """Lists workflows for the given correlation id list"""
+        return self.workflow_client.get_by_correlation_ids(
+            correlation_ids=correlation_ids,
+            workflow_name=workflow_name,
+            include_tasks=include_tasks,
+            include_completed=include_closed,
+        )
+
+    def get_by_correlation_ids_and_names(
+        self,
+        batch_request: CorrelationIdsSearchRequest,
+        include_closed: bool = None,
+        include_tasks: bool = None,
+    ) -> Dict[str, List[Workflow]]:
+        """
+        Given the list of correlation ids and list of workflow names, find and return workflows Returns a map with
+        key as correlationId and value as a list of Workflows When IncludeClosed is set to true, the return value
+        also includes workflows that are completed otherwise only running workflows are returned
+        """
+        return self.workflow_client.get_by_correlation_ids_in_batch(
+            batch_request=batch_request,
+            include_closed=include_closed,
+            include_tasks=include_tasks,
+        )
+
+    def pause(self, workflow_id: str) -> None:
+        """Pauses the workflow"""
+        return self.workflow_client.pause_workflow(workflow_id=workflow_id)
+
+    def resume(self, workflow_id: str) -> None:
+        """Resumes the workflow"""
+        return self.workflow_client.resume_workflow(workflow_id=workflow_id)
+
+    def terminate(
+        self,
+        workflow_id: str,
+        reason: str = None,
+        trigger_failure_workflow: bool = None,
+    ) -> None:
+        """Terminate workflow execution"""
+        return self.workflow_client.terminate_workflow(
+            workflow_id=workflow_id,
+            reason=reason,
+            trigger_failure_workflow=trigger_failure_workflow,
+        )
+
+    def restart(self, workflow_id: str, use_latest_definitions: bool = None) -> None:
+        """Restarts a completed workflow"""
+        return self.workflow_client.restart_workflow(
+            workflow_id=workflow_id, use_latest_def=use_latest_definitions
+        )
+
+    def retry(self, workflow_id: str, resume_subworkflow_tasks: bool = None) -> None:
+        """Retries the last failed task"""
+        return self.workflow_client.retry_workflow(
+            workflow_id=workflow_id, resume_subworkflow_tasks=resume_subworkflow_tasks
+        )
+
+    def rerun(
+        self, rerun_workflow_request: RerunWorkflowRequest, workflow_id: str
+    ) -> str:
+        """Reruns the workflow from a specific task"""
+        return self.workflow_client.rerun_workflow(
+            rerun_workflow_request=rerun_workflow_request,
+            workflow_id=workflow_id,
+        )
+
+    def skip_task_from_workflow(
+        self,
+        workflow_id: str,
+        task_reference_name: str,
+        skip_task_request: SkipTaskRequest = None,
+    ) -> None:
+        """Skips a given task from a current running workflow"""
+        return self.workflow_client.skip_task_from_workflow(
+            workflow_id=workflow_id,
+            task_reference_name=task_reference_name,
+            request=skip_task_request,
+        )
+
+    def update_task(
+        self, task_id: str, workflow_id: str, task_output: Dict[str, Any], status: str
+    ) -> str:
+        """Update a task"""
+        task_result = self.__get_task_result(task_id, workflow_id, task_output, status)
+        return self.task_client.update_task(
+            body=task_result,
+        )
+
+    def update_task_by_ref_name(
+        self,
+        task_output: Dict[str, Any],
+        workflow_id: str,
+        task_reference_name: str,
+        status: str,
+    ) -> str:
+        """Update a task By Ref Name"""
+        return self.task_client.update_task1(
+            body=task_output,
+            workflow_id=workflow_id,
+            task_ref_name=task_reference_name,
+            status=status,
+        )
+
+    def update_task_by_ref_name_sync(
+        self,
+        task_output: Dict[str, Any],
+        workflow_id: str,
+        task_reference_name: str,
+        status: str,
+    ) -> Workflow:
+        """Update a task By Ref Name"""
+        return self.task_client.update_task_sync(
+            body=task_output,
+            workflow_id=workflow_id,
+            task_ref_name=task_reference_name,
+            status=status,
+        )
+
+    def get_task(self, task_id: str) -> str:
+        """Get task by Id"""
+        return self.task_client.get_task(task_id=task_id)
+
+    def __get_task_result(
+        self, task_id: str, workflow_id: str, task_output: Dict[str, Any], status: str
+    ) -> TaskResult:
+        return TaskResult(
+            workflow_instance_id=workflow_id,
+            task_id=task_id,
+            output_data=task_output,
+            status=status,
+        )
diff --git a/omagent_core/engine/workflow/task/__init__.py b/omagent_core/engine/workflow/task/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/workflow/task/custom_task.py b/omagent_core/engine/workflow/task/custom_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..9d9143f38f6e10043c39f3fa863e7bce1501015b
--- /dev/null
+++ b/omagent_core/engine/workflow/task/custom_task.py
@@ -0,0 +1,38 @@
+from typing import List, Type, Union
+
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.engine.workflow.task.set_variable_task import SetVariableTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+from omagent_core.utils.registry import registry
+
+
+class CustomTask(TaskInterface):
+    def __init__(self, task_def_name: str, task_reference_name: str) -> Self:
+        super().__init__(
+            task_reference_name=task_reference_name,
+            task_type=TaskType.CUSTOM,
+            task_name=task_def_name,
+        )
+
+
+def custom_task(
+    task_def_name: str | Type[BaseWorker],
+    task_reference_name: str,
+    inputs: dict[str, object] = {},
+) -> TaskInterface:
+    if isinstance(task_def_name, type) and issubclass(task_def_name, BaseWorker):
+        worker_class = task_def_name
+        task_def_name = task_def_name.__name__
+        worker_class.task_type = TaskType.CUSTOM
+    else:
+        worker_class = registry.get_worker(task_def_name)
+        if worker_class:
+            worker_class.task_type = TaskType.CUSTOM
+    
+    task = CustomTask(
+        task_def_name=task_def_name, task_reference_name=task_reference_name
+    )
+    task.input_parameters.update(inputs)
+    return task
diff --git a/omagent_core/engine/workflow/task/do_while_task.py b/omagent_core/engine/workflow/task/do_while_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..d0cc009b020f8a4f8dadb813d74ebe02588d6d4f
--- /dev/null
+++ b/omagent_core/engine/workflow/task/do_while_task.py
@@ -0,0 +1,117 @@
+from copy import deepcopy
+from typing import Any, Dict, List
+
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.workflow.task.task import (
+    TaskInterface, get_task_interface_list_as_workflow_task_list)
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+def get_for_loop_condition(task_ref_name: str, iterations: int) -> str:
+    return f"if ( $.{task_ref_name}.iteration < {iterations} ) {{ true; }} else {{ false; }}"
+
+
+def get_dnc_loop_condition(task_ref_name: str) -> str:
+
+    return (
+        f" if ( $.{task_ref_name}['exit_flag'] == true) {{ false; }} else {{ true; }}"
+    )
+
+
+class DoWhileTask(TaskInterface):
+    # termination_condition is a Javascript expression that evaluates to True or False
+    def __init__(
+        self, task_ref_name: str, termination_condition: str, tasks: List[TaskInterface], inputs: Dict[str, Any] = None
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.DO_WHILE,
+            input_parameters=inputs,
+        )
+        self._loop_condition = deepcopy(termination_condition)
+        if isinstance(tasks, List):
+            self._loop_over = deepcopy(tasks)
+        else:
+            self._loop_over = [deepcopy(tasks)]
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow = super().to_workflow_task()
+        workflow.loop_condition = self._loop_condition
+        workflow.loop_over = get_task_interface_list_as_workflow_task_list(
+            *self._loop_over,
+        )
+        return workflow
+
+
+class LoopTask(DoWhileTask):
+    def __init__(
+        self, task_ref_name: str, iterations: int, tasks: List[TaskInterface]
+    ) -> Self:
+        super().__init__(
+            task_ref_name=task_ref_name,
+            termination_condition=get_for_loop_condition(
+                task_ref_name,
+                iterations,
+            ),
+            tasks=tasks,
+        )
+
+
+class ForEachTask(DoWhileTask):
+    def __init__(
+        self,
+        task_ref_name: str,
+        tasks: List[TaskInterface],
+        iterate_over: str,
+        variables: List[str] = None,
+    ) -> Self:
+        super().__init__(
+            task_ref_name=task_ref_name,
+            termination_condition=get_for_loop_condition(
+                task_ref_name,
+                0,
+            ),
+            tasks=tasks,
+        )
+        super().input_parameter("items", iterate_over)
+
+
+class InfiniteLoopTask(DoWhileTask):
+    def __init__(self, task_ref_name: str, tasks: List[TaskInterface]) -> Self:
+        super().__init__(
+            task_ref_name=task_ref_name,
+            termination_condition="true",
+            tasks=tasks,
+        )
+
+
+class DnCLoopTask(DoWhileTask):
+    def __init__(
+        self,
+        task_ref_name: str,
+        tasks: List[TaskInterface],
+        pre_loop_exit: TaskInterface = None,
+        post_loop_exit: List[TaskInterface] = None,
+    ) -> Self:
+        if pre_loop_exit is not None and post_loop_exit is not None:
+            real_tasks = pre_loop_exit + tasks + post_loop_exit
+        elif pre_loop_exit is not None:
+            real_tasks = pre_loop_exit + tasks
+        elif post_loop_exit is not None:
+            real_tasks = tasks + post_loop_exit
+        else:
+            real_tasks = tasks
+        flatten_tasks = []
+        for each in real_tasks:
+            if isinstance(each, list):
+                flatten_tasks.extend(each)
+            else:
+                flatten_tasks.append(each)
+        super().__init__(
+            task_ref_name=task_ref_name,
+            termination_condition=get_dnc_loop_condition(
+                post_loop_exit[0].task_reference_name
+            ),
+            tasks=flatten_tasks,
+        )
diff --git a/omagent_core/engine/workflow/task/dynamic_fork_task.py b/omagent_core/engine/workflow/task/dynamic_fork_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..a386d2750498502bfe098fe67ae68b81eba74f41
--- /dev/null
+++ b/omagent_core/engine/workflow/task/dynamic_fork_task.py
@@ -0,0 +1,39 @@
+from copy import deepcopy
+
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.workflow.task.join_task import JoinTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class DynamicForkTask(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        pre_fork_task: TaskInterface,
+        join_task: JoinTask = None,
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name, task_type=TaskType.FORK_JOIN_DYNAMIC
+        )
+        self._pre_fork_task = deepcopy(pre_fork_task)
+        self._join_task = deepcopy(join_task)
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow = super().to_workflow_task()
+        workflow.dynamic_fork_join_tasks_param = "forkedTasks"
+        workflow.dynamic_fork_tasks_input_param_name = "forkedTasksInputs"
+        workflow.input_parameters["forkedTasks"] = self._pre_fork_task.output_ref(
+            "forkedTasks"
+        )
+        workflow.input_parameters["forkedTasksInputs"] = self._pre_fork_task.output_ref(
+            "forkedTasksInputs"
+        )
+        tasks = [
+            self._pre_fork_task.to_workflow_task(),
+            workflow,
+        ]
+        if self._join_task != None:
+            tasks.append(self._join_task.to_workflow_task())
+        return tasks
diff --git a/omagent_core/engine/workflow/task/dynamic_task.py b/omagent_core/engine/workflow/task/dynamic_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c4860b13d6af48fc8ba10ccc775a2dbb10d2664
--- /dev/null
+++ b/omagent_core/engine/workflow/task/dynamic_task.py
@@ -0,0 +1,25 @@
+from omagent_core.engine.http.models import WorkflowTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class DynamicTask(TaskInterface):
+    def __init__(
+        self,
+        dynamic_task: str,
+        task_reference_name: str,
+        dynamic_task_param: str = "taskToExecute",
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_reference_name,
+            task_type=TaskType.DYNAMIC,
+            task_name="dynamic_task",
+        )
+        self.input_parameters[dynamic_task_param] = dynamic_task
+        self._dynamic_task_param = dynamic_task_param
+
+    def to_workflow_task(self) -> WorkflowTask:
+        wf_task = super().to_workflow_task()
+        wf_task.dynamic_task_name_param = self._dynamic_task_param
+        return wf_task
diff --git a/omagent_core/engine/workflow/task/event_task.py b/omagent_core/engine/workflow/task/event_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..086f8f8d89a5bcdc17f2fbc966476a9fe717de02
--- /dev/null
+++ b/omagent_core/engine/workflow/task/event_task.py
@@ -0,0 +1,29 @@
+from copy import deepcopy
+
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class EventTaskInterface(TaskInterface):
+    def __init__(
+        self, task_ref_name: str, event_prefix: str, event_suffix: str
+    ) -> Self:
+        super().__init__(task_reference_name=task_ref_name, task_type=TaskType.EVENT)
+        self._sink = deepcopy(event_prefix) + ":" + deepcopy(event_suffix)
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow_task = super().to_workflow_task()
+        workflow_task.sink = self._sink
+        return workflow_task
+
+
+class SqsEventTask(EventTaskInterface):
+    def __init__(self, task_ref_name: str, queue_name: str) -> Self:
+        super().__init__(task_ref_name, "sqs", queue_name)
+
+
+class ConductorEventTask(EventTaskInterface):
+    def __init__(self, task_ref_name: str, event_name: str) -> Self:
+        super().__init__(task_ref_name, "conductor", event_name)
diff --git a/omagent_core/engine/workflow/task/fork_task.py b/omagent_core/engine/workflow/task/fork_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e8e39af74ca1cc98296d6456174d3b79654f91f
--- /dev/null
+++ b/omagent_core/engine/workflow/task/fork_task.py
@@ -0,0 +1,51 @@
+from copy import deepcopy
+from typing import List
+
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.workflow.task.join_task import JoinTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+def get_join_task(task_reference_name: str) -> str:
+    return task_reference_name + "_join"
+
+
+class ForkTask(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        forked_tasks: List[List[TaskInterface]],
+        join_on: List[str] = None,
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name, task_type=TaskType.FORK_JOIN
+        )
+        self._forked_tasks = deepcopy(forked_tasks)
+        self._join_on = join_on
+
+    def to_workflow_task(self) -> [WorkflowTask]:
+        tasks = []
+        workflow_task = super().to_workflow_task()
+        workflow_task.fork_tasks = []
+        workflow_task.join_on = []
+        for inner_forked_tasks in self._forked_tasks:
+            converted_inner_forked_tasks = []
+            for inner_forked_task in inner_forked_tasks:
+                converted_inner_forked_tasks.append(
+                    inner_forked_task.to_workflow_task()
+                )
+            workflow_task.fork_tasks.append(converted_inner_forked_tasks)
+            workflow_task.join_on.append(
+                converted_inner_forked_tasks[-1].task_reference_name
+            )
+        if self._join_on is not None:
+            join_on = self._join_on
+            join_task = JoinTask(
+                workflow_task.task_reference_name + "_join", join_on=join_on
+            )
+            tasks.append(workflow_task)
+            tasks.append(join_task.to_workflow_task())
+            return tasks
+        return workflow_task
diff --git a/omagent_core/engine/workflow/task/get_document.py b/omagent_core/engine/workflow/task/get_document.py
new file mode 100644
index 0000000000000000000000000000000000000000..905602a835d032c146f48ed00a5fbfea962f8f22
--- /dev/null
+++ b/omagent_core/engine/workflow/task/get_document.py
@@ -0,0 +1,15 @@
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class GetDocument(TaskInterface):
+    def __init__(
+        self, task_name: str, task_ref_name: str, url: str, media_type: str
+    ) -> Self:
+        super().__init__(
+            task_name=task_name,
+            task_reference_name=task_ref_name,
+            task_type=TaskType.GET_DOCUMENT,
+            input_parameters={"url": url, "mediaType": media_type},
+        )
diff --git a/omagent_core/engine/workflow/task/http_poll_task.py b/omagent_core/engine/workflow/task/http_poll_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..3121c2110bebdfe1711dbf9fe865260c0129e93d
--- /dev/null
+++ b/omagent_core/engine/workflow/task/http_poll_task.py
@@ -0,0 +1,78 @@
+from copy import deepcopy
+from enum import Enum
+from typing import Any, Dict, List, Union
+
+from omagent_core.engine.workflow.task.http_task import (HttpInput, HttpMethod,
+                                                         HttpTask)
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class HttpPollInput:
+    swagger_types = {
+        "_uri": "str",
+        "_method": "str",
+        "_accept": "list[str]",
+        "_headers": "dict[str, list[str]]",
+        "_content_type": "str",
+        "_connection_time_out": "int",
+        "_read_timeout": "int",
+        "_body": "str",
+        "_termination_condition": "str",
+        "_polling_interval": "int",
+        "_max_poll_count": "int",
+        "_polling_strategy": str,
+    }
+
+    attribute_map = {
+        "_uri": "uri",
+        "_method": "method",
+        "_accept": "accept",
+        "_headers": "headers",
+        "_content_type": "contentType",
+        "_connection_time_out": "connectionTimeOut",
+        "_read_timeout": "readTimeOut",
+        "_body": "body",
+        "_termination_condition": "terminationCondition",
+        "_polling_interval": "pollingInterval",
+        "_max_poll_count": "maxPollCount",
+        "_polling_strategy": "pollingStrategy",
+    }
+
+    def __init__(
+        self,
+        termination_condition: str = None,
+        max_poll_count: int = 100,
+        polling_interval: int = 100,
+        polling_strategy: str = "FIXED",
+        method: HttpMethod = HttpMethod.GET,
+        uri: str = None,
+        headers: Dict[str, List[str]] = None,
+        accept: str = None,
+        content_type: str = None,
+        connection_time_out: int = None,
+        read_timeout: int = None,
+        body: Any = None,
+    ) -> Self:
+        self._method = deepcopy(method)
+        self._uri = deepcopy(uri)
+        self._headers = deepcopy(headers)
+        self._accept = deepcopy(accept)
+        self._content_type = deepcopy(content_type)
+        self._connection_time_out = deepcopy(connection_time_out)
+        self._read_timeout = deepcopy(read_timeout)
+        self._body = deepcopy(body)
+        self._termination_condition = termination_condition
+        self._max_poll_count = max_poll_count
+        self._polling_interval = polling_interval
+        self._polling_strategy = polling_strategy
+
+
+class HttpPollTask(TaskInterface):
+    def __init__(self, task_ref_name: str, http_input: HttpPollInput) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.HTTP_POLL,
+            input_parameters={"http_request": http_input},
+        )
diff --git a/omagent_core/engine/workflow/task/http_task.py b/omagent_core/engine/workflow/task/http_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..7c25555b4ecfffc12408a631148341bfd60152d6
--- /dev/null
+++ b/omagent_core/engine/workflow/task/http_task.py
@@ -0,0 +1,94 @@
+from copy import deepcopy
+from enum import Enum
+from typing import Any, Dict, List, Union
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class HttpMethod(str, Enum):
+    GET = ("GET",)
+    PUT = ("PUT",)
+    POST = ("POST",)
+    DELETE = ("DELETE",)
+    HEAD = ("HEAD",)
+    OPTIONS = "OPTIONS"
+
+
+class HttpInput:
+    swagger_types = {
+        "_uri": "str",
+        "_method": "str",
+        "_accept": "list[str]",
+        "_headers": "dict[str, list[str]]",
+        "_content_type": "str",
+        "_connection_time_out": "int",
+        "_read_timeout": "int",
+        "_body": "str",
+    }
+
+    attribute_map = {
+        "_uri": "uri",
+        "_method": "method",
+        "_accept": "accept",
+        "_headers": "headers",
+        "_content_type": "contentType",
+        "_connection_time_out": "connectionTimeOut",
+        "_read_timeout": "readTimeOut",
+        "_body": "body",
+    }
+
+    def __init__(
+        self,
+        method: HttpMethod = HttpMethod.GET,
+        uri: str = None,
+        headers: Dict[str, List[str]] = None,
+        accept: str = None,
+        content_type: str = None,
+        connection_time_out: int = None,
+        read_timeout: int = None,
+        body: Any = None,
+    ) -> Self:
+        self._method = deepcopy(method)
+        self._uri = deepcopy(uri)
+        self._headers = deepcopy(headers)
+        self._accept = deepcopy(accept)
+        self._content_type = deepcopy(content_type)
+        self._connection_time_out = deepcopy(connection_time_out)
+        self._read_timeout = deepcopy(read_timeout)
+        self._body = deepcopy(body)
+
+
+class HttpTask(TaskInterface):
+    def __init__(self, task_ref_name: str, http_input: Union[HttpInput, dict]) -> Self:
+        if type(http_input) is dict and "method" not in http_input:
+            http_input["method"] = "GET"
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.HTTP,
+            input_parameters={"http_request": http_input},
+        )
+
+    def status_code(self) -> int:
+        return "${" + f"{self.task_reference_name}.output.response.statusCode" + "}"
+
+    def headers(self, json_path: str = None) -> str:
+        if json_path is None:
+            return "${" + f"{self.task_reference_name}.output.response.headers" + "}"
+        else:
+            return (
+                "${"
+                + f"{self.task_reference_name}.output.response.headers.{json_path}"
+                + "}"
+            )
+
+    def body(self, json_path: str = None) -> str:
+        if json_path is None:
+            return "${" + f"{self.task_reference_name}.output.response.body" + "}"
+        else:
+            return (
+                "${"
+                + f"{self.task_reference_name}.output.response.body.{json_path}"
+                + "}"
+            )
diff --git a/omagent_core/engine/workflow/task/human_task.py b/omagent_core/engine/workflow/task/human_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..c990c82492732b98d06cb8374658c690b8026f92
--- /dev/null
+++ b/omagent_core/engine/workflow/task/human_task.py
@@ -0,0 +1,49 @@
+from enum import Enum
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class AssignmentCompletionStrategy(str, Enum):
+    LEAVE_OPEN = ("LEAVE_OPEN",)
+    TERMINATE = "TERMINATE"
+
+    def __str__(self) -> str:
+        return self.name.__str__()
+
+
+class TriggerType(str, Enum):
+    ASSIGNED = ("ASSIGNED",)
+    PENDING = ("PENDING",)
+    IN_PROGRESS = ("IN_PROGRESS",)
+    COMPLETED = ("COMPLETED",)
+    TIMED_OUT = ("TIMED_OUT",)
+    ASSIGNEE_CHANGED = ("ASSIGNEE_CHANGED",)
+
+    def __str__(self) -> str:
+        return self.name.__str__()
+
+
+class HumanTask(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        display_name: str = None,
+        form_template: str = None,
+        form_version: int = 0,
+        assignment_completion_strategy: AssignmentCompletionStrategy = AssignmentCompletionStrategy.LEAVE_OPEN,
+    ) -> Self:
+        super().__init__(task_reference_name=task_ref_name, task_type=TaskType.HUMAN)
+        self.input_parameters.update(
+            {
+                "__humanTaskDefinition": {
+                    "assignmentCompletionStrategy": assignment_completion_strategy.name,
+                    "displayName": display_name,
+                    "userFormTemplate": {
+                        "name": form_template,
+                        "version": form_version,
+                    },
+                }
+            }
+        )
diff --git a/omagent_core/engine/workflow/task/inline.py b/omagent_core/engine/workflow/task/inline.py
new file mode 100644
index 0000000000000000000000000000000000000000..83e363ccc95a6a6dfa5867c7be6525878f4b71d9
--- /dev/null
+++ b/omagent_core/engine/workflow/task/inline.py
@@ -0,0 +1,19 @@
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class InlineTask(TaskInterface):
+    def __init__(
+        self, task_ref_name: str, script: str, bindings: dict[str, str] = None
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.INLINE,
+            input_parameters={
+                "evaluatorType": "graaljs",
+                "expression": script,
+            },
+        )
+        if bindings is not None:
+            self.input_parameters.update(bindings)
diff --git a/omagent_core/engine/workflow/task/javascript_task.py b/omagent_core/engine/workflow/task/javascript_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..224853d70f22c79bc48e3cb365211f6f81ca22ed
--- /dev/null
+++ b/omagent_core/engine/workflow/task/javascript_task.py
@@ -0,0 +1,29 @@
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class JavascriptTask(TaskInterface):
+    def __init__(
+        self, task_ref_name: str, script: str, bindings: dict[str, str] = None
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.INLINE,
+            input_parameters={
+                "evaluatorType": "graaljs",
+                "expression": script,
+            },
+        )
+        if bindings is not None:
+            self.input_parameters.update(bindings)
+
+    def output(self, json_path: str = None) -> str:
+        if json_path is None:
+            return "${" + f"{self.task_reference_name}.output.result" + "}"
+        else:
+            return "${" + f"{self.task_reference_name}.output.result.{json_path}" + "}"
+
+    def evaluator_type(self, evaluator_type: str) -> Self:
+        self.input_parameters["evaluatorType"] = evaluator_type
+        return self
diff --git a/omagent_core/engine/workflow/task/join_task.py b/omagent_core/engine/workflow/task/join_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..c1e0b76cea4c986a9dd3507f8f8eabb09435acb0
--- /dev/null
+++ b/omagent_core/engine/workflow/task/join_task.py
@@ -0,0 +1,23 @@
+from copy import deepcopy
+from typing import List
+
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class JoinTask(TaskInterface):
+    def __init__(
+        self, task_ref_name: str, join_on: List[str] = None, join_on_script: str = None
+    ) -> Self:
+        super().__init__(task_reference_name=task_ref_name, task_type=TaskType.JOIN)
+        self._join_on = deepcopy(join_on)
+        if join_on_script is not None:
+            self.evaluator_type = "js"
+            self.expression = join_on_script
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow = super().to_workflow_task()
+        workflow.join_on = self._join_on
+        return workflow
diff --git a/omagent_core/engine/workflow/task/json_jq_task.py b/omagent_core/engine/workflow/task/json_jq_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c1da35d6d97ae82f824a754f18168ecc79b0226
--- /dev/null
+++ b/omagent_core/engine/workflow/task/json_jq_task.py
@@ -0,0 +1,12 @@
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class JsonJQTask(TaskInterface):
+    def __init__(self, task_ref_name: str, script: str) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.JSON_JQ_TRANSFORM,
+            input_parameters={"queryExpression": script},
+        )
diff --git a/omagent_core/engine/workflow/task/kafka_publish.py b/omagent_core/engine/workflow/task/kafka_publish.py
new file mode 100644
index 0000000000000000000000000000000000000000..ff28776b8a88cc5390b9d03e4499d762c55a7d3d
--- /dev/null
+++ b/omagent_core/engine/workflow/task/kafka_publish.py
@@ -0,0 +1,16 @@
+from omagent_core.engine.workflow.task.kafka_publish_input import \
+    KafkaPublishInput
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class KafkaPublishTask(TaskInterface):
+    def __init__(
+        self, task_ref_name: str, kafka_publish_input: KafkaPublishInput = None
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.KAFKA_PUBLISH,
+            input_parameters={"kafka_request": kafka_publish_input},
+        )
diff --git a/omagent_core/engine/workflow/task/kafka_publish_input.py b/omagent_core/engine/workflow/task/kafka_publish_input.py
new file mode 100644
index 0000000000000000000000000000000000000000..c003337c1e5df4167cef71b1e49109e340ef267a
--- /dev/null
+++ b/omagent_core/engine/workflow/task/kafka_publish_input.py
@@ -0,0 +1,26 @@
+from copy import deepcopy
+from typing import Any, Dict
+
+from typing_extensions import Self
+
+
+class KafkaPublishInput:
+    def __init__(
+        self,
+        bootstrap_servers: str = None,
+        key: str = None,
+        key_serializer: str = None,
+        value: str = None,
+        request_timeout_ms: str = None,
+        max_block_ms: str = None,
+        headers: Dict[str, Any] = None,
+        topic: str = None,
+    ) -> Self:
+        self._bootstrap_servers = deepcopy(bootstrap_servers)
+        self._key = deepcopy(key)
+        self._key_serializer = deepcopy(key_serializer)
+        self._value = deepcopy(value)
+        self._request_timeout_ms = deepcopy(request_timeout_ms)
+        self._max_block_ms = deepcopy(max_block_ms)
+        self._headers = deepcopy(headers)
+        self._topic = deepcopy(topic)
diff --git a/omagent_core/engine/workflow/task/llm_tasks/__init__.py b/omagent_core/engine/workflow/task/llm_tasks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/workflow/task/llm_tasks/llm_chat_complete.py b/omagent_core/engine/workflow/task/llm_tasks/llm_chat_complete.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd2b0791ba5affe1367c70e8d47d22c537852281
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/llm_chat_complete.py
@@ -0,0 +1,62 @@
+from typing import List, Optional
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class ChatMessage:
+
+    def __init__(self, role: str, message: str) -> None:
+        self.role = role
+        self.message = message
+
+
+class LlmChatComplete(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        llm_provider: str,
+        model: str,
+        messages: List[ChatMessage],
+        stop_words: Optional[List[str]] = [],
+        max_tokens: Optional[int] = 100,
+        temperature: int = 0,
+        top_p: int = 1,
+        instructions_template: str = None,
+        template_variables: dict[str, object] = {},
+    ) -> Self:
+        optional_input_params = {}
+
+        if stop_words:
+            optional_input_params.update({"stopWords": stop_words})
+
+        if max_tokens:
+            optional_input_params.update({"maxTokens": max_tokens})
+
+        input_params = {
+            "llmProvider": llm_provider,
+            "model": model,
+            "promptVariables": template_variables,
+            "temperature": temperature,
+            "topP": top_p,
+            "instructions": instructions_template,
+            "messages": messages,
+        }
+
+        input_params.update(optional_input_params)
+
+        super().__init__(
+            task_name="llm_chat_complete",
+            task_reference_name=task_ref_name,
+            task_type=TaskType.LLM_CHAT_COMPLETE,
+            input_parameters=input_params,
+        )
+
+    def prompt_variables(self, variables: dict[str, object]) -> Self:
+        self.input_parameters["promptVariables"].update(variables)
+        return self
+
+    def prompt_variable(self, variable: str, value: object) -> Self:
+        self.input_parameters["promptVariables"][variable] = value
+        return self
diff --git a/omagent_core/engine/workflow/task/llm_tasks/llm_generate_embeddings.py b/omagent_core/engine/workflow/task/llm_tasks/llm_generate_embeddings.py
new file mode 100644
index 0000000000000000000000000000000000000000..620f1501da5a36712704eff555a04ffbd701e8aa
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/llm_generate_embeddings.py
@@ -0,0 +1,26 @@
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class LlmGenerateEmbeddings(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        llm_provider: str,
+        model: str,
+        text: str,
+        task_name: str = None,
+    ) -> Self:
+        if task_name is None:
+            task_name = "llm_generate_embeddings"
+        super().__init__(
+            task_name=task_name,
+            task_reference_name=task_ref_name,
+            task_type=TaskType.LLM_GENERATE_EMBEDDINGS,
+            input_parameters={
+                "llmProvider": llm_provider,
+                "model": model,
+                "text": text,
+            },
+        )
diff --git a/omagent_core/engine/workflow/task/llm_tasks/llm_index_documents.py b/omagent_core/engine/workflow/task/llm_tasks/llm_index_documents.py
new file mode 100644
index 0000000000000000000000000000000000000000..150977f777ca28f7854f642813be269d732497a9
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/llm_index_documents.py
@@ -0,0 +1,74 @@
+from typing import Optional
+
+from omagent_core.engine.workflow.task.llm_tasks.utils.embedding_model import \
+    EmbeddingModel
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class LlmIndexDocument(TaskInterface):
+    """
+    Indexes the document specified by a URL
+    Inputs:
+    embedding_model.provider: AI provider to use for generating embeddings e.g. OpenAI
+    embedding_model.model: Model to be used to generate embeddings e.g. text-embedding-ada-002
+    url: URL to read the document from.  Can be HTTP(S), S3 or other blob store that the server can access
+    media_type: content type for the document. e.g. application/pdf, text/html, text/plain, application/json, text/json
+    namespace: (optional) namespace to separate the data inside the index - if supported by vector store (e.g. Pinecone)
+    index: Index or classname (in case of Weaviate)
+
+    Optional fields
+    chunk_size: size of the chunk so the document is split into the chunks and stored
+    chunk_overlap: how much the chunks should overlap
+    doc_id: by default the indexed document is given an id based on the URL, use doc_id to override this
+    metadata: a dictionary of optional metadata to be added to thd indexed doc
+    """
+
+    def __init__(
+        self,
+        task_ref_name: str,
+        vector_db: str,
+        namespace: str,
+        embedding_model: EmbeddingModel,
+        index: str,
+        url: str,
+        media_type: str,
+        chunk_size: Optional[int] = None,
+        chunk_overlap: Optional[int] = None,
+        doc_id: str = None,
+        task_name: str = None,
+        metadata: dict = {},
+    ) -> Self:
+        input_params = {
+            "vectorDB": vector_db,
+            "namespace": namespace,
+            "index": index,
+            "embeddingModelProvider": embedding_model.provider,
+            "embeddingModel": embedding_model.model,
+            "url": url,
+            "mediaType": media_type,
+            "metadata": metadata,
+        }
+
+        optional_input_params = {}
+
+        if chunk_size:
+            optional_input_params.update({"chunkSize": chunk_size})
+
+        if chunk_overlap:
+            optional_input_params.update({"chunkOverlap": chunk_overlap})
+
+        if doc_id:
+            optional_input_params.update({"docId": doc_id})
+
+        input_params.update(optional_input_params)
+        if task_name is None:
+            task_name = "llm_index_document"
+
+        super().__init__(
+            task_name=task_name,
+            task_reference_name=task_ref_name,
+            task_type=TaskType.LLM_INDEX_DOCUMENT,
+            input_parameters=input_params,
+        )
diff --git a/omagent_core/engine/workflow/task/llm_tasks/llm_index_text.py b/omagent_core/engine/workflow/task/llm_tasks/llm_index_text.py
new file mode 100644
index 0000000000000000000000000000000000000000..5150c7fdc3c120f6b41622a22299cd80cc4d7601
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/llm_index_text.py
@@ -0,0 +1,52 @@
+from omagent_core.engine.workflow.task.llm_tasks.utils.embedding_model import \
+    EmbeddingModel
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class LlmIndexText(TaskInterface):
+    """
+    Stores the text as ebmeddings in the vector database
+    Inputs:
+    embedding_model.provider: AI provider to use for generating embeddings e.g. OpenAI
+    embedding_model.model: Model to be used to generate embeddings e.g. text-embedding-ada-002
+    url: URL to read the document from.  Can be HTTP(S), S3 or other blob store that the server can access
+    media_type: content type for the document. e.g. application/pdf, text/html, text/plain, application/json, text/json
+    namespace: (optional) namespace to separate the data inside the index - if supported by vector store (e.g. Pinecone)
+    index: Index or classname (in case of Weaviate)
+    doc_id: ID of the stored document in the vector db
+    metadata: a dictionary of optional metadata to be added to thd indexed doc
+    """
+
+    def __init__(
+        self,
+        task_ref_name: str,
+        vector_db: str,
+        index: str,
+        embedding_model: EmbeddingModel,
+        text: str,
+        doc_id: str,
+        namespace: str = None,
+        task_name: str = None,
+        metadata: dict = {},
+    ) -> Self:
+        if task_name is None:
+            task_name = "llm_index_doc"
+
+        super().__init__(
+            task_name=task_name,
+            task_reference_name=task_ref_name,
+            task_type=TaskType.LLM_INDEX_TEXT,
+            input_parameters={
+                "vectorDB": vector_db,
+                "index": index,
+                "embeddingModelProvider": embedding_model.provider,
+                "embeddingModel": embedding_model.model,
+                "text": text,
+                "docId": doc_id,
+                "metadata": metadata,
+            },
+        )
+        if namespace is not None:
+            self.input_parameter("namespace", namespace)
diff --git a/omagent_core/engine/workflow/task/llm_tasks/llm_query_embeddings.py b/omagent_core/engine/workflow/task/llm_tasks/llm_query_embeddings.py
new file mode 100644
index 0000000000000000000000000000000000000000..85cd6edf29b1828be07676551a9ced4aae8f4036
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/llm_query_embeddings.py
@@ -0,0 +1,31 @@
+from typing import List
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class LlmQueryEmbeddings(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        vector_db: str,
+        index: str,
+        embeddings: List[int],
+        task_name: str = None,
+        namespace: str = None,
+    ) -> Self:
+        if task_name is None:
+            task_name = "llm_get_embeddings"
+
+        super().__init__(
+            task_name=task_name,
+            task_reference_name=task_ref_name,
+            task_type=TaskType.LLM_GET_EMBEDDINGS,
+            input_parameters={
+                "vectorDB": vector_db,
+                "namespace": namespace,
+                "index": index,
+                "embeddings": embeddings,
+            },
+        )
diff --git a/omagent_core/engine/workflow/task/llm_tasks/llm_search_index.py b/omagent_core/engine/workflow/task/llm_tasks/llm_search_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..b59bc6544b6959bab40d0340aa30087f88e1179c
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/llm_search_index.py
@@ -0,0 +1,35 @@
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class LlmSearchIndex(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        vector_db: str,
+        namespace: str,
+        index: str,
+        embedding_model_provider: str,
+        embedding_model: str,
+        query: str,
+        task_name: str = None,
+        max_results: int = 1,
+    ) -> Self:
+        if task_name is None:
+            task_name = "llm_search_index"
+
+        super().__init__(
+            task_name=task_name,
+            task_reference_name=task_ref_name,
+            task_type=TaskType.LLM_SEARCH_INDEX,
+            input_parameters={
+                "vectorDB": vector_db,
+                "namespace": namespace,
+                "index": index,
+                "embeddingModelProvider": embedding_model_provider,
+                "embeddingModel": embedding_model,
+                "query": query,
+                "maxResults": max_results,
+            },
+        )
diff --git a/omagent_core/engine/workflow/task/llm_tasks/llm_text_complete.py b/omagent_core/engine/workflow/task/llm_tasks/llm_text_complete.py
new file mode 100644
index 0000000000000000000000000000000000000000..d55e6e3db973b194e6e40d06c963362892ac9122
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/llm_text_complete.py
@@ -0,0 +1,57 @@
+from typing import List, Optional
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class LlmTextComplete(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        llm_provider: str,
+        model: str,
+        prompt_name: str,
+        stop_words: Optional[List[str]] = [],
+        max_tokens: Optional[int] = 100,
+        temperature: int = 0,
+        top_p: int = 1,
+        task_name: str = None,
+    ) -> Self:
+        optional_input_params = {}
+
+        if stop_words:
+            optional_input_params.update({"stopWords": stop_words})
+
+        if max_tokens:
+            optional_input_params.update({"maxTokens": max_tokens})
+
+        if not task_name:
+            task_name = "llm_text_complete"
+
+        input_params = {
+            "llmProvider": llm_provider,
+            "model": model,
+            "promptName": prompt_name,
+            "promptVariables": {},
+            "temperature": temperature,
+            "topP": top_p,
+        }
+
+        input_params.update(optional_input_params)
+
+        super().__init__(
+            task_name=task_name,
+            task_reference_name=task_ref_name,
+            task_type=TaskType.LLM_TEXT_COMPLETE,
+            input_parameters=input_params,
+        )
+        self.input_parameters["promptVariables"] = {}
+
+    def prompt_variables(self, variables: dict[str, object]) -> Self:
+        self.input_parameters["promptVariables"].update(variables)
+        return self
+
+    def prompt_variable(self, variable: str, value: object) -> Self:
+        self.input_parameters["promptVariables"][variable] = value
+        return self
diff --git a/omagent_core/engine/workflow/task/llm_tasks/utils/__init__.py b/omagent_core/engine/workflow/task/llm_tasks/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/engine/workflow/task/llm_tasks/utils/embedding_model.py b/omagent_core/engine/workflow/task/llm_tasks/utils/embedding_model.py
new file mode 100644
index 0000000000000000000000000000000000000000..b61d67e24a658d337b7bff1dfe950354317cc29d
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/utils/embedding_model.py
@@ -0,0 +1,24 @@
+class EmbeddingModel(object):
+    swagger_types = {"provider": "str", "model": "str"}
+
+    attribute_map = {"provider": "embeddingModelProvider", "model": "embeddingModel"}
+
+    def __init__(self, provider: str, model: str):
+        self._provider = provider
+        self._model = model
+
+    @property
+    def provider(self) -> str:
+        return self._provider
+
+    @property
+    def model(self) -> str:
+        return self._model
+
+    @provider.setter
+    def provider(self, provider: str):
+        self._provider = provider
+
+    @model.setter
+    def model(self, model: str):
+        self._model = model
diff --git a/omagent_core/engine/workflow/task/llm_tasks/utils/prompt.py b/omagent_core/engine/workflow/task/llm_tasks/utils/prompt.py
new file mode 100644
index 0000000000000000000000000000000000000000..6bf33f0b66ec39779698917917cb33f33a00016b
--- /dev/null
+++ b/omagent_core/engine/workflow/task/llm_tasks/utils/prompt.py
@@ -0,0 +1,24 @@
+class Prompt(object):
+    swagger_types = {"name": "str", "variables": "str"}
+
+    attribute_map = {"name": "promptName", "variables": "promptVariables"}
+
+    def __init__(self, name: str, variables: dict[str, object]):
+        self._name = name
+        self._variables = variables
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @property
+    def variables(self) -> str:
+        return self._variables
+
+    @name.setter
+    def name(self, name: str):
+        self._name = name
+
+    @variables.setter
+    def variables(self, variables: str):
+        self._variables = variables
diff --git a/omagent_core/engine/workflow/task/set_variable_task.py b/omagent_core/engine/workflow/task/set_variable_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..ded84d1dc5155e1c6ebdfb6dceb53d6d85264706
--- /dev/null
+++ b/omagent_core/engine/workflow/task/set_variable_task.py
@@ -0,0 +1,14 @@
+from typing import Any, Dict
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class SetVariableTask(TaskInterface):
+    def __init__(self, task_ref_name: str, input_parameters: Dict[str, Any]) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.SET_VARIABLE,
+            input_parameters=input_parameters,
+        )
diff --git a/omagent_core/engine/workflow/task/simple_task.py b/omagent_core/engine/workflow/task/simple_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..52874ec4f2a53a929f0fb429f88da006591e8e28
--- /dev/null
+++ b/omagent_core/engine/workflow/task/simple_task.py
@@ -0,0 +1,30 @@
+from typing import List, Type, Union
+
+from omagent_core.engine.worker.base import BaseWorker
+from omagent_core.engine.workflow.task.set_variable_task import SetVariableTask
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class SimpleTask(TaskInterface):
+    def __init__(self, task_def_name: str, task_reference_name: str) -> Self:
+        super().__init__(
+            task_reference_name=task_reference_name,
+            task_type=TaskType.SIMPLE,
+            task_name=task_def_name,
+        )
+
+
+def simple_task(
+    task_def_name: str | Type[BaseWorker],
+    task_reference_name: str,
+    inputs: dict[str, object] = {},
+) -> TaskInterface:
+    if isinstance(task_def_name, type) and issubclass(task_def_name, BaseWorker):
+        task_def_name = task_def_name.__name__
+    task = SimpleTask(
+        task_def_name=task_def_name, task_reference_name=task_reference_name
+    )
+    task.input_parameters.update(inputs)
+    return task
diff --git a/omagent_core/engine/workflow/task/start_workflow_task.py b/omagent_core/engine/workflow/task/start_workflow_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..406aed89fae4dceb37ea2906eb41967b2e19156e
--- /dev/null
+++ b/omagent_core/engine/workflow/task/start_workflow_task.py
@@ -0,0 +1,27 @@
+from omagent_core.engine.http.models.start_workflow_request import \
+    StartWorkflowRequest
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class StartWorkflowTask(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        workflow_name: str,
+        start_workflow_request: StartWorkflowRequest,
+        version: int = None,
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.START_WORKFLOW,
+            input_parameters={
+                "startWorkflow": {
+                    "name": workflow_name,
+                    "version": version,
+                    "input": start_workflow_request.input,
+                    "correlationId": start_workflow_request.correlation_id,
+                },
+            },
+        )
diff --git a/omagent_core/engine/workflow/task/sub_workflow_task.py b/omagent_core/engine/workflow/task/sub_workflow_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a776043b7c7a694727b1630ecd805c0bdb50c62
--- /dev/null
+++ b/omagent_core/engine/workflow/task/sub_workflow_task.py
@@ -0,0 +1,55 @@
+from copy import deepcopy
+from typing import Dict
+
+from omagent_core.engine.http.models.sub_workflow_params import \
+    SubWorkflowParams
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class SubWorkflowTask(TaskInterface):
+    def __init__(
+        self,
+        task_ref_name: str,
+        workflow_name: str,
+        version: int = None,
+        task_to_domain_map: Dict[str, str] = None,
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name, task_type=TaskType.SUB_WORKFLOW
+        )
+        self._workflow_name = deepcopy(workflow_name)
+        self._version = deepcopy(version)
+        self._task_to_domain_map = deepcopy(task_to_domain_map)
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow = super().to_workflow_task()
+        workflow.sub_workflow_param = SubWorkflowParams(
+            name=self._workflow_name,
+            version=self._version,
+            task_to_domain=self._task_to_domain_map,
+        )
+        return workflow
+
+
+class InlineSubWorkflowTask(TaskInterface):
+    def __init__(self, task_ref_name: str, workflow: ConductorWorkflow) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.SUB_WORKFLOW,
+        )
+        self._workflow_name = deepcopy(workflow.name)
+        self._workflow_version = deepcopy(workflow.version)
+        self._workflow_definition = deepcopy(workflow.to_workflow_def())
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow = super().to_workflow_task()
+        workflow.sub_workflow_param = SubWorkflowParams(
+            name=self._workflow_name,
+            version=self._workflow_version,
+            workflow_definition=self._workflow_definition,
+        )
+        return workflow
diff --git a/omagent_core/engine/workflow/task/switch_task.py b/omagent_core/engine/workflow/task/switch_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2a2219c97052c1a05b84d08e76d5c7e089c18ab
--- /dev/null
+++ b/omagent_core/engine/workflow/task/switch_task.py
@@ -0,0 +1,66 @@
+from copy import deepcopy
+from enum import Enum
+from typing import List
+
+from omagent_core.engine.http.models.workflow_task import WorkflowTask
+from omagent_core.engine.workflow.task.task import (
+    TaskInterface, get_task_interface_list_as_workflow_task_list)
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class EvaluatorType(str, Enum):
+    JAVASCRIPT = ("javascript",)
+    ECMASCRIPT = ("graaljs",)
+    VALUE_PARAM = "value-param"
+
+
+class SwitchTask(TaskInterface):
+    def __init__(
+        self, task_ref_name: str, case_expression: str, use_javascript: bool = False
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.SWITCH,
+        )
+        self._default_case = None
+        self._decision_cases = {}
+        self._expression = deepcopy(case_expression)
+        self._use_javascript = deepcopy(use_javascript)
+
+    def switch_case(self, case_name: str, tasks: List[TaskInterface]) -> Self:
+        if isinstance(tasks, List):
+            self._decision_cases[case_name] = deepcopy(tasks)
+        else:
+            self._decision_cases[case_name] = [deepcopy(tasks)]
+        return self
+
+    def default_case(self, tasks: List[TaskInterface]) -> Self:
+        if isinstance(tasks, List):
+            self._default_case = deepcopy(tasks)
+        else:
+            self._default_case = [deepcopy(tasks)]
+        return self
+
+    def to_workflow_task(self) -> WorkflowTask:
+        workflow = super().to_workflow_task()
+        if self._use_javascript:
+            workflow.evaluator_type = EvaluatorType.ECMASCRIPT
+            workflow.expression = self._expression
+        else:
+            workflow.evaluator_type = EvaluatorType.VALUE_PARAM
+            workflow.input_parameters["switchCaseValue"] = self._expression
+            workflow.expression = "switchCaseValue"
+        workflow.decision_cases = {}
+        for case_value, tasks in self._decision_cases.items():
+            workflow.decision_cases[case_value] = (
+                get_task_interface_list_as_workflow_task_list(
+                    *tasks,
+                )
+            )
+        if self._default_case is None:
+            self._default_case = []
+        workflow.default_case = get_task_interface_list_as_workflow_task_list(
+            *self._default_case
+        )
+        return workflow
diff --git a/omagent_core/engine/workflow/task/task.py b/omagent_core/engine/workflow/task/task.py
new file mode 100644
index 0000000000000000000000000000000000000000..bd197174fe3d0b0fa1caadd4ebfeabdacc31dd89
--- /dev/null
+++ b/omagent_core/engine/workflow/task/task.py
@@ -0,0 +1,182 @@
+from abc import ABC, abstractmethod
+from copy import deepcopy
+from typing import Any, Dict, List
+
+from omagent_core.engine.http.models.workflow_task import (CacheConfig,
+                                                           WorkflowTask)
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self, Union
+
+
+def get_task_interface_list_as_workflow_task_list(*tasks: Self) -> List[WorkflowTask]:
+    converted_tasks = []
+    for task in tasks:
+        converted_tasks.append(task.to_workflow_task())
+    return converted_tasks
+
+
+class TaskInterface(ABC):
+    @abstractmethod
+    def __init__(
+        self,
+        task_reference_name: str,
+        task_type: TaskType,
+        task_name: str = None,
+        description: str = None,
+        optional: bool = None,
+        input_parameters: Dict[str, Any] = None,
+        cache_key: str = None,
+        cache_ttl_second: int = 0,
+    ) -> Self:
+        self.task_reference_name = task_reference_name
+        self.task_type = task_type
+        self.name = task_name or task_reference_name
+        self.description = description
+        self.optional = optional
+        self.input_parameters = input_parameters
+        self._cache_key = cache_key
+        self._cache_ttl_second = cache_ttl_second
+        self._expression = None
+        self._evaluator_type = None
+
+    @property
+    def task_reference_name(self) -> str:
+        return self._task_reference_name
+
+    @task_reference_name.setter
+    def task_reference_name(self, task_reference_name: str) -> None:
+        if not isinstance(task_reference_name, str):
+            raise Exception("invalid type")
+        self._task_reference_name = deepcopy(task_reference_name)
+
+    @property
+    def task_type(self) -> TaskType:
+        return self._task_type
+
+    @task_type.setter
+    def task_type(self, task_type: TaskType) -> None:
+        if not isinstance(task_type, TaskType):
+            raise Exception("invalid type")
+        self._task_type = deepcopy(task_type)
+
+    @property
+    def name(self) -> str:
+        return self._name
+
+    @name.setter
+    def name(self, name: str) -> None:
+        if not isinstance(name, str):
+            raise Exception("invalid type")
+        self._name = name
+
+    @property
+    def expression(self) -> str:
+        return self._expression
+
+    @expression.setter
+    def expression(self, expression: str) -> None:
+        self._expression = expression
+
+    @property
+    def evaluator_type(self) -> str:
+        return self._evaluator_type
+
+    @evaluator_type.setter
+    def evaluator_type(self, evaluator_type: str) -> None:
+        self._evaluator_type = evaluator_type
+
+    def cache(self, cache_key: str, cache_ttl_second: int):
+        self._cache_key = cache_key
+        self._cache_ttl_second = cache_ttl_second
+
+    @property
+    def description(self) -> str:
+        return self._description
+
+    @description.setter
+    def description(self, description: str) -> None:
+        if description != None and not isinstance(description, str):
+            raise Exception("invalid type")
+        self._description = deepcopy(description)
+
+    @property
+    def optional(self) -> bool:
+        return self._optional
+
+    @optional.setter
+    def optional(self, optional: bool) -> None:
+        if optional is not None and not isinstance(optional, bool):
+            raise Exception("invalid type")
+        self._optional = deepcopy(optional)
+
+    @property
+    def input_parameters(self) -> Dict[str, Any]:
+        return self._input_parameters
+
+    @input_parameters.setter
+    def input_parameters(self, input_parameters: Dict[str, Any]) -> None:
+        if input_parameters is None:
+            self._input_parameters = {}
+            return
+        if not isinstance(input_parameters, dict):
+            try:
+                self._input_parameters = input_parameters.__dict__
+            except:
+                raise Exception(f"invalid type: {type(input_parameters)}")
+        self._input_parameters = deepcopy(input_parameters)
+
+    def input_parameter(self, key: str, value: Any) -> Self:
+        if not isinstance(key, str):
+            raise Exception("invalid type")
+        self._input_parameters[key] = deepcopy(value)
+        return self
+
+    def to_workflow_task(self) -> WorkflowTask:
+        cache_config = None
+        if self._cache_ttl_second > 0 and self._cache_key is not None:
+            cache_config = CacheConfig(
+                key=self._cache_key, ttl_in_second=self._cache_ttl_second
+            )
+        return WorkflowTask(
+            name=self._name,
+            task_reference_name=self._task_reference_name,
+            type=self._task_type.value,
+            description=self._description,
+            input_parameters=self._input_parameters,
+            optional=self._optional,
+            cache_config=cache_config,
+            expression=self._expression,
+            evaluator_type=self._evaluator_type,
+        )
+
+    def output(self, json_path: str = None) -> str:
+        if json_path is None:
+            return "${" + f"{self.task_reference_name}.output" + "}"
+        else:
+            if json_path.startswith("."):
+                return "${" + f"{self.task_reference_name}.output{json_path}" + "}"
+            else:
+                return "${" + f"{self.task_reference_name}.output.{json_path}" + "}"
+
+    def input(
+        self, json_path: str = None, key: str = None, value: Any = None
+    ) -> Union[str, Self]:
+        if key is not None and value is not None:
+            """
+            For the backwards compatibility
+            """
+            return self.input_parameter(key, value)
+
+        if json_path is None:
+            return "${" + f"{self.task_reference_name}.input" + "}"
+        else:
+            return "${" + f"{self.task_reference_name}.input.{json_path}" + "}"
+
+    def __getattribute__(self, __name: str) -> Any:
+        try:
+            val = super().__getattribute__(__name)
+            return val
+        except AttributeError as ae:
+            if not __name.startswith("_"):
+                return "${" + self.task_reference_name + ".output." + __name + "}"
+            raise ae
diff --git a/omagent_core/engine/workflow/task/task_type.py b/omagent_core/engine/workflow/task/task_type.py
new file mode 100644
index 0000000000000000000000000000000000000000..a48a6b064c032efc70dde21086cd2ff0c17d630d
--- /dev/null
+++ b/omagent_core/engine/workflow/task/task_type.py
@@ -0,0 +1,37 @@
+from enum import Enum
+
+
+class TaskType(str, Enum):
+    CUSTOM = "custom"
+    SIMPLE = "SIMPLE"
+    DYNAMIC = "DYNAMIC"
+    FORK_JOIN = "FORK_JOIN"
+    FORK_JOIN_DYNAMIC = "FORK_JOIN_DYNAMIC"
+    DECISION = "DECISION"
+    SWITCH = "SWITCH"
+    JOIN = "JOIN"
+    DO_WHILE = "DO_WHILE"
+    SUB_WORKFLOW = "SUB_WORKFLOW"
+    START_WORKFLOW = "START_WORKFLOW"
+    EVENT = "EVENT"
+    WAIT = "WAIT"
+    WAIT_FOR_WEBHOOK = "WAIT_FOR_WEBHOOK"
+    HUMAN = "HUMAN"
+    USER_DEFINED = "USER_DEFINED"
+    HTTP = "HTTP"
+    HTTP_POLL = "HTTP_POLL"
+    LAMBDA = "LAMBDA"
+    INLINE = "INLINE"
+    EXCLUSIVE_JOIN = "EXCLUSIVE_JOIN"
+    TERMINATE = "TERMINATE"
+    KAFKA_PUBLISH = "KAFKA_PUBLISH"
+    JSON_JQ_TRANSFORM = "JSON_JQ_TRANSFORM"
+    SET_VARIABLE = "SET_VARIABLE"
+    GET_DOCUMENT = "GET_DOCUMENT"
+    LLM_GENERATE_EMBEDDINGS = "LLM_GENERATE_EMBEDDINGS"
+    LLM_GET_EMBEDDINGS = "LLM_GET_EMBEDDINGS"
+    LLM_TEXT_COMPLETE = "LLM_TEXT_COMPLETE"
+    LLM_CHAT_COMPLETE = "LLM_CHAT_COMPLETE"
+    LLM_INDEX_TEXT = "LLM_INDEX_TEXT"
+    LLM_INDEX_DOCUMENT = "LLM_INDEX_DOCUMENT"
+    LLM_SEARCH_INDEX = "LLM_SEARCH_INDEX"
diff --git a/omagent_core/engine/workflow/task/terminate_task.py b/omagent_core/engine/workflow/task/terminate_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..26eea42a555643c282fd788ed5818fe90df7ecb9
--- /dev/null
+++ b/omagent_core/engine/workflow/task/terminate_task.py
@@ -0,0 +1,28 @@
+from enum import Enum
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class WorkflowStatus(str, Enum):
+    COMPLETED = ("COMPLETED",)
+    FAILED = ("FAILED",)
+    PAUSED = ("PAUSED",)
+    RUNNING = ("RUNNING",)
+    TERMINATED = ("TERMINATED",)
+    TIMEOUT_OUT = ("TIMED_OUT",)
+
+
+class TerminateTask(TaskInterface):
+    def __init__(
+        self, task_ref_name: str, status: WorkflowStatus, termination_reason: str
+    ) -> Self:
+        super().__init__(
+            task_reference_name=task_ref_name,
+            task_type=TaskType.TERMINATE,
+            input_parameters={
+                "terminationStatus": status,
+                "terminationReason": termination_reason,
+            },
+        )
diff --git a/omagent_core/engine/workflow/task/timeout_policy.py b/omagent_core/engine/workflow/task/timeout_policy.py
new file mode 100644
index 0000000000000000000000000000000000000000..d32d3eb72bd16f7cb519036e9335a89c37ca66ac
--- /dev/null
+++ b/omagent_core/engine/workflow/task/timeout_policy.py
@@ -0,0 +1,6 @@
+from enum import Enum
+
+
+class TimeoutPolicy(str, Enum):
+    TIME_OUT_WORKFLOW = ("TIME_OUT_WF",)
+    ALERT_ONLY = ("ALERT_ONLY",)
diff --git a/omagent_core/engine/workflow/task/wait_for_webhook_task.py b/omagent_core/engine/workflow/task/wait_for_webhook_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..fd7cacd969f47e2ca3f9c26f76e814f0f9b33289
--- /dev/null
+++ b/omagent_core/engine/workflow/task/wait_for_webhook_task.py
@@ -0,0 +1,44 @@
+from abc import ABC
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class WaitForWebHookTask(TaskInterface, ABC):
+
+    def __init__(self, task_ref_name: str, matches: dict[str, object]) -> Self:
+        """
+        matches: dictionary of matching payload that acts as correction between the incoming webhook payload and a
+        running workflow task - amongst all the running workflows.
+
+        example:
+        if the matches is specified as below:
+
+        {
+            "$['type']": "customer_created",
+            "$['customer_id']": "${workflow.input.customer_id}"
+        }
+
+        for an incoming webhook request with the payload like:
+        {
+         "type": "customer_created",
+         "customer_id": "customer_123"
+        }
+
+        The system will find a matching workflow task that is in progress matching the type and customer id and complete
+        the task.
+        """
+        super().__init__(
+            task_reference_name=task_ref_name, task_type=TaskType.WAIT_FOR_WEBHOOK
+        )
+        self.input_parameters["matches"] = matches
+
+
+def wait_for_webhook(
+    task_ref_name: str, matches: dict[str, object], task_def_name: str = None
+) -> TaskInterface:
+    task = WaitForWebHookTask(task_ref_name=task_ref_name, matches=matches)
+    if task_def_name is not None:
+        task.name = task_def_name
+    return task
diff --git a/omagent_core/engine/workflow/task/wait_task.py b/omagent_core/engine/workflow/task/wait_task.py
new file mode 100644
index 0000000000000000000000000000000000000000..62fd6714c441b74304f11ee94cc3b1f64eab7a67
--- /dev/null
+++ b/omagent_core/engine/workflow/task/wait_task.py
@@ -0,0 +1,37 @@
+from abc import ABC
+
+from omagent_core.engine.workflow.task.task import TaskInterface
+from omagent_core.engine.workflow.task.task_type import TaskType
+from typing_extensions import Self
+
+
+class WaitTask(TaskInterface, ABC):
+
+    def __init__(
+        self, task_ref_name: str, wait_until: str = None, wait_for_seconds: int = None
+    ) -> Self:
+        """
+        wait_until: Specific date/time to wait for e.g. 2023-12-25 05:25 PST
+        wait_for_seconds: time to block for - e.g. specifying 60 will wait for 60 seconds
+        """
+        super().__init__(task_reference_name=task_ref_name, task_type=TaskType.WAIT)
+        if wait_until is not None and wait_for_seconds is not None:
+            raise Exception(
+                "both wait_until and wait_for_seconds are provided.  ONLY one is allowed"
+            )
+        if wait_until:
+            self.input_parameters = {"wait_until": wait_until}
+        if wait_for_seconds:
+            self.input_parameters = {"duration": str(wait_for_seconds) + "s"}
+
+
+class WaitForDurationTask(WaitTask):
+    def __init__(self, task_ref_name: str, duration_time_seconds: int) -> Self:
+        super().__init__(task_ref_name)
+        self.input_parameters = {"duration": str(duration_time_seconds) + "s"}
+
+
+class WaitUntilTask(WaitTask):
+    def __init__(self, task_ref_name: str, date_time: str) -> Self:
+        super().__init__(task_ref_name)
+        self.input_parameters = {"until": date_time}
diff --git a/omagent_core/engine/workflow_client.py b/omagent_core/engine/workflow_client.py
new file mode 100644
index 0000000000000000000000000000000000000000..7fdbe77fae1f6016114b604671848e5fcff7860c
--- /dev/null
+++ b/omagent_core/engine/workflow_client.py
@@ -0,0 +1,144 @@
+from abc import ABC, abstractmethod
+from typing import List, Optional
+
+from omagent_core.engine.http.models import (
+    ScrollableSearchResultWorkflowSummary, SkipTaskRequest, WorkflowRun,
+    WorkflowStatus)
+from omagent_core.engine.http.models.correlation_ids_search_request import \
+    CorrelationIdsSearchRequest
+from omagent_core.engine.http.models.rerun_workflow_request import \
+    RerunWorkflowRequest
+from omagent_core.engine.http.models.start_workflow_request import \
+    StartWorkflowRequest
+from omagent_core.engine.http.models.workflow import Workflow
+from omagent_core.engine.http.models.workflow_state_update import \
+    WorkflowStateUpdate
+from omagent_core.engine.http.models.workflow_test_request import \
+    WorkflowTestRequest
+
+
+class WorkflowClient(ABC):
+    @abstractmethod
+    def start_workflow(self, start_workflow_request: StartWorkflowRequest) -> str:
+        pass
+
+    @abstractmethod
+    def get_workflow(
+        self, workflow_id: str, include_tasks: Optional[bool] = True
+    ) -> Workflow:
+        pass
+
+    @abstractmethod
+    def get_workflow_status(
+        self,
+        workflow_id: str,
+        include_output: bool = None,
+        include_variables: bool = None,
+    ) -> WorkflowStatus:
+        pass
+
+    @abstractmethod
+    def delete_workflow(
+        self, workflow_id: str, archive_workflow: Optional[bool] = True
+    ):
+        pass
+
+    @abstractmethod
+    def terminate_workflow(
+        self,
+        workflow_id: str,
+        reason: Optional[str] = None,
+        trigger_failure_workflow: bool = False,
+    ):
+        pass
+
+    @abstractmethod
+    def execute_workflow(
+        self,
+        start_workflow_request: StartWorkflowRequest,
+        request_id: str = None,
+        wait_until_task_ref: Optional[str] = None,
+        wait_for_seconds: int = 30,
+    ) -> WorkflowRun:
+        pass
+
+    @abstractmethod
+    def pause_workflow(self, workflow_id: str):
+        pass
+
+    @abstractmethod
+    def resume_workflow(self, workflow_id: str):
+        pass
+
+    @abstractmethod
+    def restart_workflow(
+        self, workflow_id: str, use_latest_def: Optional[bool] = False
+    ):
+        pass
+
+    @abstractmethod
+    def retry_workflow(
+        self, workflow_id: str, resume_subworkflow_tasks: Optional[bool] = False
+    ):
+        pass
+
+    @abstractmethod
+    def rerun_workflow(
+        self, workflow_id: str, rerun_workflow_request: RerunWorkflowRequest
+    ):
+        pass
+
+    @abstractmethod
+    def skip_task_from_workflow(
+        self, workflow_id: str, task_reference_name: str, request: SkipTaskRequest
+    ):
+        pass
+
+    @abstractmethod
+    def test_workflow(self, test_request: WorkflowTestRequest) -> Workflow:
+        pass
+
+    @abstractmethod
+    def search(
+        self, start: int = 0, size: int = 100, free_text: str = "*", query: str = None
+    ) -> ScrollableSearchResultWorkflowSummary:
+        pass
+
+    @abstractmethod
+    def get_by_correlation_ids_in_batch(
+        self,
+        batch_request: CorrelationIdsSearchRequest,
+        include_completed: bool = False,
+        include_tasks: bool = False,
+    ) -> dict[str, List[Workflow]]:
+        pass
+
+    @abstractmethod
+    def get_by_correlation_ids(
+        self,
+        workflow_name: str,
+        correlation_ids: List[str],
+        include_completed: bool = False,
+        include_tasks: bool = False,
+    ) -> dict[str, List[Workflow]]:
+        pass
+
+    @abstractmethod
+    def remove_workflow(self, workflow_id: str):
+        pass
+
+    @abstractmethod
+    def update_variables(
+        self, workflow_id: str, variables: dict[str, object] = {}
+    ) -> None:
+        pass
+
+    @abstractmethod
+    def update_state(
+        self,
+        workflow_id: str,
+        update_requesst: WorkflowStateUpdate,
+        wait_until_task_ref_names: List[str] = None,
+        wait_for_seconds: int = None,
+    ) -> WorkflowRun:
+        pass
diff --git a/omagent_core/memories/ltms/__init__.py b/omagent_core/memories/ltms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/memories/ltms/ltm.py b/omagent_core/memories/ltms/ltm.py
new file mode 100644
index 0000000000000000000000000000000000000000..12f27c7785cc20196081944c754ba8ca208766d5
--- /dev/null
+++ b/omagent_core/memories/ltms/ltm.py
@@ -0,0 +1,13 @@
+from pydantic import BaseModel
+
+
+class LTM(BaseModel):
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    def handler_register(self, name: str, handler):
+        self.__setattr__(name, handler)
diff --git a/omagent_core/memories/ltms/ltm_base.py b/omagent_core/memories/ltms/ltm_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..492cfc148827b044b6c908b6b0252e1d77fc8486
--- /dev/null
+++ b/omagent_core/memories/ltms/ltm_base.py
@@ -0,0 +1,147 @@
+from abc import ABC, abstractmethod
+from typing import Any, Iterable, Tuple
+
+from omagent_core.base import BotBase
+
+
+class LTMBase(BotBase):
+    @abstractmethod
+    def __getitem__(self, key: Any) -> Any:
+        """
+        Retrieve an item from the short-term memory by its key.
+
+        Args:
+            key (Any): The key of the item to retrieve.
+
+        Returns:
+            Any: The value associated with the given key.
+
+        Raises:
+            KeyError: If the key is not found in the short-term memory.
+        """
+        pass
+
+    @abstractmethod
+    def __setitem__(self, key: Any, value: Any) -> None:
+        """
+        Set an item in the short-term memory.
+
+        Args:
+            key (Any): The key of the item to set.
+            value (Any): The value to associate with the key.
+        """
+        pass
+
+    @abstractmethod
+    def __delitem__(self, key: Any) -> None:
+        """
+        Delete an item from the short-term memory.
+
+        Args:
+            key (Any): The key of the item to delete.
+
+        Raises:
+            KeyError: If the key is not found in the short-term memory.
+        """
+        pass
+
+    @abstractmethod
+    def __contains__(self, key: Any) -> bool:
+        """
+        Check if a key exists in the short-term memory.
+
+        Args:
+            key (Any): The key to check for.
+
+        Returns:
+            bool: True if the key exists, False otherwise.
+        """
+        pass
+
+    @abstractmethod
+    def __len__(self) -> int:
+        """
+        Get the number of items in the short-term memory.
+
+        Returns:
+            int: The number of items in the short-term memory.
+        """
+        pass
+
+    @abstractmethod
+    def keys(self) -> Iterable[Any]:
+        """
+        Get an iterable of all keys in the short-term memory.
+
+        Returns:
+            Iterable[Any]: An iterable containing all keys.
+        """
+        pass
+
+    @abstractmethod
+    def values(self) -> Iterable[Any]:
+        """
+        Get an iterable of all values in the short-term memory.
+
+        Returns:
+            Iterable[Any]: An iterable containing all values.
+        """
+        pass
+
+    @abstractmethod
+    def items(self) -> Iterable[Tuple[Any, Any]]:
+        """
+        Get an iterable of all key-value pairs in the short-term memory.
+
+        Returns:
+            Iterable[Tuple[Any, Any]]: An iterable containing all key-value pairs.
+        """
+        pass
+
+    @abstractmethod
+    def get(self, key: Any, default: Any = None) -> Any:
+        """
+        Get an item from the short-term memory.
+
+        Args:
+            key (Any): The key of the item to retrieve.
+            default (Any, optional): The default value to return if the key is not found. Defaults to None.
+
+        Returns:
+            Any: The value associated with the key, or the default value if the key is not found.
+        """
+        pass
+
+    @abstractmethod
+    def clear(self) -> None:
+        """
+        Remove all items from the short-term memory.
+        """
+        pass
+
+    @abstractmethod
+    def pop(self, key: Any, default: Any = None) -> Any:
+        """
+        Remove and return an item from the short-term memory.
+
+        Args:
+            key (Any): The key of the item to remove.
+            default (Any, optional): The default value to return if the key is not found. Defaults to None.
+
+        Returns:
+            Any: The value associated with the key, or the default value if the key is not found.
+
+        Raises:
+            KeyError: If the key is not found and no default value is provided.
+        """
+        pass
+
+    @abstractmethod
+    def update(self, other: Iterable[Tuple[Any, Any]]) -> None:
+        """
+        Update the short-term memory with key-value pairs from another iterable.
+
+        Args:
+            other (Iterable[Tuple[Any, Any]]): An iterable of key-value pairs to update the short-term memory with.
+        """
+        pass
diff --git a/omagent_core/memories/ltms/ltm_milvus.py b/omagent_core/memories/ltms/ltm_milvus.py
new file mode 100644
index 0000000000000000000000000000000000000000..a16b7e2a510e8cffc2deead8452f1b7bc722d149
--- /dev/null
+++ b/omagent_core/memories/ltms/ltm_milvus.py
@@ -0,0 +1,270 @@
+import base64
+import pickle
+from typing import Any, Iterable, List, Optional, Tuple
+
+from omagent_core.memories.ltms.ltm_base import LTMBase
+from omagent_core.services.connectors.milvus import MilvusConnector
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from pymilvus import (Collection, CollectionSchema, DataType, FieldSchema,
+                      utility)
+
+
+@registry.register_component()
+class MilvusLTM(LTMBase):
+    milvus_ltm_client: MilvusConnector
+    storage_name: str = Field(default="default")
+    dim: int = Field(default=128)
+
+    def model_post_init(self, __context: Any) -> None:
+        pass
+
+    def _create_collection(self) -> None:
+        # Check if collection exists
+        if not self.milvus_ltm_client._client.has_collection(self.storage_name):
+            index_params = self.milvus_ltm_client._client.prepare_index_params()
+            # Define field schemas
+            key_field = FieldSchema(
+                name="key", dtype=DataType.VARCHAR, is_primary=True, max_length=256
+            )
+            value_field = FieldSchema(
+                name="value",
+                dtype=DataType.VARCHAR,
+                description="Serialized value",
+                max_length=65535,
+            )
+            embedding_field = FieldSchema(
+                name="embedding",
+                dtype=DataType.FLOAT_VECTOR,
+                description="Embedding vector",
+                dim=self.dim,
+            )
+            index_params = self.milvus_ltm_client._client.prepare_index_params()
+
+            # Create collection schema
+            schema = CollectionSchema(
+                fields=[key_field, value_field, embedding_field],
+                description="Key-Value storage with embeddings",
+            )
+            for field in schema.fields:
+                if (
+                    field.dtype == DataType.FLOAT_VECTOR
+                    or field.dtype == DataType.BINARY_VECTOR
+                ):
+                    index_params.add_index(
+                        field_name=field.name,
+                        index_name=field.name,
+                        index_type="FLAT",
+                        metric_type="COSINE",
+                        params={"nlist": 128},
+                    )
+            self.milvus_ltm_client._client.create_collection(
+                self.storage_name, schema=schema, index_params=index_params
+            )
+
+            # Create index separately after collection creation
+            print(f"Created storage {self.storage_name} successfully")
+
+    def __getitem__(self, key: Any) -> Any:
+        key_str = str(key)
+        expr = f'key == "{key_str}"'
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["value"]
+        )
+        if res:
+            value_base64 = res[0]["value"]
+            value_bytes = base64.b64decode(value_base64)
+            value = pickle.loads(value_bytes)
+            return value
+        else:
+            raise KeyError(f"Key {key} not found")
+
+    def __setitem__(self, key: Any, value: Any) -> None:
+        self._create_collection()
+
+        key_str = str(key)
+
+        # Check if value is a dictionary containing 'value' and 'embedding'
+        if isinstance(value, dict) and "value" in value and "embedding" in value:
+            actual_value = value["value"]
+            embedding = value["embedding"]
+        else:
+            raise ValueError(
+                "When setting an item, value must be a dictionary containing 'value' and 'embedding' keys."
+            )
+
+        # Serialize the actual value and encode it to base64
+        value_bytes = pickle.dumps(actual_value)
+        value_base64 = base64.b64encode(value_bytes).decode("utf-8")
+
+        # Ensure the embedding is provided
+        if embedding is None:
+            raise ValueError("An embedding vector must be provided.")
+
+        # Check if the key exists and delete it if it does
+        if key_str in self:
+            self.__delitem__(key_str)
+
+        # Prepare data for insertion (as a list of dictionaries)
+        data = [
+            {
+                "key": key_str,
+                "value": value_base64,
+                "embedding": embedding,
+            }
+        ]
+
+        # Insert the new record
+        self.milvus_ltm_client._client.insert(
+            collection_name=self.storage_name, data=data
+        )
+
+    def __delitem__(self, key: Any) -> None:
+        key_str = str(key)
+        if key_str in self:
+            expr = f'key == "{key_str}"'
+            self.milvus_ltm_client._client.delete(self.storage_name, expr)
+        else:
+            raise KeyError(f"Key {key} not found")
+
+    def __contains__(self, key: Any) -> bool:
+        key_str = str(key)
+        expr = f'key == "{key_str}"'
+        # Adjust the query call to match the expected signature
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name,  # Pass the collection name as the first argument
+            filter=expr,
+            output_fields=["key"],
+        )
+        return len(res) > 0
+
+    """
+    def __len__(self) -> int:
+        milvus_ltm.collection.flush()
+        return self.collection.num_entities
+    """
+
+    def __len__(self) -> int:
+        expr = 'key != ""'  # Expression to match all entities
+        # self.milvus_ltm_client._client.load(refresh=True)
+        results = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["key"], consistency_level="Strong"
+        )
+        return len(results)
+
+    def keys(self, limit=10) -> Iterable[Any]:
+        expr = ""
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["key"], limit=limit
+        )
+        return (item["key"] for item in res)
+
+    def values(self) -> Iterable[Any]:
+        expr = 'key != ""'  # Expression to match all active entities
+        self.milvus_ltm_client._client.load(refresh=True)
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["value"], consistency_level="Strong"
+        )
+        for item in res:
+            value_base64 = item["value"]
+            value_bytes = base64.b64decode(value_base64)
+            value = pickle.loads(value_bytes)
+            yield value
+
+    def items(self) -> Iterable[Tuple[Any, Any]]:
+        expr = 'key != ""'
+        res = self.milvus_ltm_client._client.query(
+            self.storage_name, expr, output_fields=["key", "value"]
+        )
+        for item in res:
+            key = item["key"]
+            value_base64 = item["value"]
+            value_bytes = base64.b64decode(value_base64)
+            value = pickle.loads(value_bytes)
+            yield (key, value)
+
+    def get(self, key: Any, default: Any = None) -> Any:
+        try:
+            return self[key]
+        except KeyError:
+            return default
+
+    def clear(self) -> None:
+        expr = (
+            'key != ""'  # This expression matches all records where 'key' is not empty
+        )
+        self.milvus_ltm_client._client.delete(self.storage_name, filter=expr)
+
+    def pop(self, key: Any, default: Any = None) -> Any:
+        try:
+            value = self[key]
+            self.__delitem__(key)
+            return value
+        except KeyError:
+            if default is not None:
+                return default
+            else:
+                raise
+
+    def update(self, other: Iterable[Tuple[Any, Any]]) -> None:
+        for key, value in other:
+            self[key] = value
+
+    def get_by_vector(
+        self, embedding: List[float], top_k: int = 10, threshold: float = 0.0
+    ) -> List[Tuple[Any, Any, float]]:
+        search_params = {
+            "metric_type": "COSINE",
+            "params": {"nprobe": 10},
+        }
+        results = self.milvus_ltm_client._client.search(
+            self.storage_name,
+            data=[embedding],
+            anns_field="embedding",
+            search_params=search_params,
+            limit=top_k,
+            output_fields=["key", "value"],
+            consistency_level="Strong",
+        )
+
+        items = []
+        for match in results[0]:
+            key = match.get("entity").get("key")
+            value_base64 = match.get("entity").get("value")
+            value_bytes = base64.b64decode(value_base64)
+            value = pickle.loads(value_bytes)
+            distance = match["distance"]
+            if distance >= threshold:
+                items.append((key, value, distance))
+
+        return items
+
+
+if __name__ == "__main__":
+    milvus_ltm_client = MilvusConnector()
+    milvus_ltm = MilvusLTM(
+        milvus_ltm_client=milvus_ltm_client, dim=128, storage_name="test3"
+    )
+    # print(milvus_ltm["key"])
+
+    embedding_vector = [0.1] * 128  # Replace with actual 128-dimensional vector
+    milvus_ltm["0"] = {"value": {"abc": "abc"}, "embedding": embedding_vector}
+    milvus_ltm.clear()
+
+    print(milvus_ltm["0"])
+
+    print(len(milvus_ltm))
+    for key in milvus_ltm.keys():
+        print(key)
+    for value in milvus_ltm.items():
+        print(value)
+
+    # Set an item with an embedding
+    embedding_vector = [0.1] * 128  # Replace with actual 128-dimensional vector
+    # milvus_ltm.__setitem__('key', 'value', embedding=embedding_vector)
+
+    # Search for similar items
+    query_embedding = [0.15] * 128  # Replace with actual 128-dimensional vector
+    similar_items = milvus_ltm.get_by_vector(query_embedding, top_k=5)
+    for key, value, distance in similar_items:
+        print(f"Key: {key}, Value: {value}, Distance: {distance}")
diff --git a/omagent_core/memories/stms/__init__.py b/omagent_core/memories/stms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/memories/stms/stm_base.py b/omagent_core/memories/stms/stm_base.py
new file mode 100644
index 0000000000000000000000000000000000000000..89a638eaf6c043c567c2eef410ccc6a29f251bcd
--- /dev/null
+++ b/omagent_core/memories/stms/stm_base.py
@@ -0,0 +1,218 @@
+from abc import ABC, abstractmethod
+from typing import Any
+
+from omagent_core.base import BotBase
+
+
+class STMBase(BotBase):
+    @abstractmethod
+    def __call__(self, workflow_instance_id: str):
+        """
+        Return a WorkflowInstanceProxy for the given workflow instance ID.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            WorkflowInstanceProxy: A proxy object for accessing the workflow instance data.
+        """
+        pass
+
+    @abstractmethod
+    def __getitem__(self, key: tuple | str) -> Any:
+        """
+        Retrieve an item or all items from storage.
+
+        Args:
+            key (tuple | str): Either a tuple containing (workflow_instance_id, key) or just workflow_instance_id
+
+        Returns:
+            Any: The value associated with the given key, or a dictionary of all key-value pairs for the workflow_instance_id.
+
+        Raises:
+            KeyError: If the key is not found in storage.
+        """
+        pass
+
+    @abstractmethod
+    def __setitem__(self, key: tuple, value: Any) -> None:
+        """
+        Set an item in storage.
+
+        Args:
+            key (tuple): A tuple containing (workflow_instance_id, key)
+            value (Any): The value to associate with the key.
+        """
+        pass
+
+    @abstractmethod
+    def __delitem__(self, workflow_instance_id: str, key: str) -> None:
+        """
+        Delete an item from storage.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to delete.
+
+        Raises:
+            KeyError: If the key is not found in storage.
+        """
+        pass
+
+    @abstractmethod
+    def __contains__(self, key: tuple) -> bool:
+        """
+        Check if a key exists in storage.
+
+        Args:
+            key (tuple): A tuple containing (workflow_instance_id, key)
+
+        Returns:
+            bool: True if the key exists, False otherwise.
+        """
+        pass
+
+    @abstractmethod
+    def keys(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all keys in storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all keys.
+        """
+        pass
+
+    @abstractmethod
+    def values(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all values in storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all values.
+        """
+        pass
+
+    @abstractmethod
+    def items(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all key-value pairs in storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all key-value pairs.
+        """
+        pass
+
+    @abstractmethod
+    def get(self, workflow_instance_id: str, key: str, default: Any = None) -> Any:
+        """
+        Get an item from storage.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to retrieve.
+            default (Any, optional): The default value to return if the key is not found. Defaults to None.
+
+        Returns:
+            Any: The value associated with the given key, or the default value if the key is not found.
+        """
+        pass
+
+    @abstractmethod
+    def clear(self, workflow_instance_id: str) -> None:
+        """
+        Remove all items from storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+        """
+        pass
+
+    @abstractmethod
+    def pop(self, workflow_instance_id: str, key: str, default: Any = None) -> Any:
+        """
+        Remove and return an item from storage.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to remove.
+            default (Any, optional): The default value to return if the key is not found. Defaults to None.
+
+        Returns:
+            Any: The value associated with the given key, or the default value if the key is not found.
+        """
+        pass
+
+    @abstractmethod
+    def update(self, workflow_instance_id: str, other: dict) -> None:
+        """
+        Update storage with the key-value pairs from another dictionary.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            other (dict): A dictionary containing the key-value pairs to update with.
+        """
+        pass
+
+    @abstractmethod
+    def __len__(self, workflow_instance_id: str) -> int:
+        """
+        Get the number of items in storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            int: The number of items in storage.
+        """
+        pass
+
+
+class WorkflowInstanceProxy:
+    def __init__(self, stm: STMBase, workflow_instance_id: str):
+        self.stm = stm
+        self.workflow_instance_id = workflow_instance_id
+
+    def __getitem__(self, key: str) -> Any:
+        return self.stm[self.workflow_instance_id, key]
+
+    def __setitem__(self, key: str, value: Any) -> None:
+        self.stm[self.workflow_instance_id, key] = value
+
+    def __delitem__(self, key: str) -> None:
+        self.stm.__delitem__(self.workflow_instance_id, key)
+
+    def __contains__(self, key: str) -> bool:
+        return (self.workflow_instance_id, key) in self.stm
+
+    def keys(self) -> list:
+        return self.stm.keys(self.workflow_instance_id)
+
+    def values(self) -> list:
+        return self.stm.values(self.workflow_instance_id)
+
+    def items(self) -> list:
+        return self.stm.items(self.workflow_instance_id)
+
+    def get(self, key: str, default: Any = None) -> Any:
+        return self.stm.get(self.workflow_instance_id, key, default)
+
+    def clear(self) -> None:
+        self.stm.clear(self.workflow_instance_id)
+
+    def pop(self, key: str, default: Any = None) -> Any:
+        return self.stm.pop(self.workflow_instance_id, key, default)
+
+    def update(self, other: dict) -> None:
+        self.stm.update(self.workflow_instance_id, other)
+
+    def __len__(self) -> int:
+        return self.stm.__len__(self.workflow_instance_id)
diff --git a/omagent_core/memories/stms/stm_redis.py b/omagent_core/memories/stms/stm_redis.py
new file mode 100644
index 0000000000000000000000000000000000000000..12bb5cee2ba92486957314cd721efc7e9381b379
--- /dev/null
+++ b/omagent_core/memories/stms/stm_redis.py
@@ -0,0 +1,250 @@
+import pickle
+from typing import Any
+
+from omagent_core.memories.stms.stm_base import STMBase, WorkflowInstanceProxy
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.registry import registry
+
+
+@registry.register_component()
+class RedisSTM(STMBase):
+    redis_stm_client: RedisConnector
+
+    def __call__(self, workflow_instance_id: str):
+        """
+        Return a WorkflowInstanceProxy for the given workflow instance ID.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            WorkflowInstanceProxy: A proxy object for accessing the workflow instance data.
+        """
+        return WorkflowInstanceProxy(self, workflow_instance_id)
+
+    def __getitem__(self, key: tuple | str) -> Any:
+        """
+        Retrieve an item or all items from the Redis storage.
+
+        Args:
+            key (tuple | str): Either a tuple containing (workflow_instance_id, key) or just workflow_instance_id
+
+        Returns:
+            Any: The value associated with the given key, or a dictionary of all key-value pairs for the workflow_instance_id.
+
+        Raises:
+            KeyError: If the key is not found in the Redis storage.
+        """
+        if isinstance(key, tuple):
+            workflow_instance_id, key = key
+            value = self.redis_stm_client._client.hget(workflow_instance_id, key)
+            if value is not None:
+                return pickle.loads(value)
+            raise KeyError(key)
+        else:
+            workflow_instance_id = key
+            all_items = self.redis_stm_client._client.hgetall(workflow_instance_id)
+            if not all_items:
+                return {}
+            return {k.decode("utf-8"): pickle.loads(v) for k, v in all_items.items()}
+
+    def __setitem__(self, key: tuple, value: Any) -> None:
+        """
+        Set an item in the Redis storage.
+
+        Args:
+            key (tuple): A tuple containing (workflow_instance_id, key)
+            value (Any): The value to associate with the key.
+        """
+        workflow_instance_id, key = key
+        self.redis_stm_client._client.hset(
+            workflow_instance_id, key, pickle.dumps(value)
+        )
+
+    def __delitem__(self, workflow_instance_id: str, key: str) -> None:
+        """
+        Delete an item from the Redis storage.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to delete.
+
+        Raises:
+            KeyError: If the key is not found in the Redis storage.
+        """
+        if not self.redis_stm_client._client.hdel(workflow_instance_id, key):
+            raise KeyError(key)
+
+    def __contains__(self, key: tuple) -> bool:
+        """
+        Check if a key exists in the Redis storage.
+
+        Args:
+            key (tuple): A tuple containing (workflow_instance_id, key)
+
+        Returns:
+            bool: True if the key exists, False otherwise.
+        """
+        workflow_instance_id, key = key
+        return self.redis_stm_client._client.hexists(workflow_instance_id, key)
+
+    def keys(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all keys in the Redis storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all keys.
+        """
+        return [
+            k.decode("utf-8")
+            for k in self.redis_stm_client._client.hkeys(workflow_instance_id)
+        ]
+
+    def values(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all values in the Redis storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all values.
+        """
+        return [
+            pickle.loads(v)
+            for v in self.redis_stm_client._client.hvals(workflow_instance_id)
+        ]
+
+    def items(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all key-value pairs in the Redis storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all key-value pairs.
+        """
+        items = self.redis_stm_client._client.hgetall(workflow_instance_id)
+        return [(k.decode("utf-8"), pickle.loads(v)) for k, v in items.items()]
+
+    def get(self, workflow_instance_id: str, key: str, default: Any = None) -> Any:
+        """
+        Get an item from the Redis storage.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to retrieve.
+            default (Any, optional): The default value to return if the key is not found. Defaults to None.
+
+        Returns:
+            Any: The value associated with the given key, or the default value if the key is not found.
+        """
+        try:
+            return self[workflow_instance_id, key]
+        except KeyError:
+            return default
+
+    def clear(self, workflow_instance_id: str) -> None:
+        """
+        Remove all items from the Redis storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+        """
+        self.redis_stm_client._client.delete(workflow_instance_id)
+
+    def pop(self, workflow_instance_id: str, key: str, default: Any = None) -> Any:
+        """
+        Remove and return an item from the Redis storage.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to remove.
+            default (Any, optional): The default value to return if the key is not found. Defaults to None.
+
+        Returns:
+            Any: The value associated with the given key, or the default value if the key is not found.
+        """
+        try:
+            value = self[workflow_instance_id, key]
+            self.redis_stm_client._client.hdel(workflow_instance_id, key)
+            return value
+        except KeyError:
+            return default
+
+    def update(self, workflow_instance_id: str, other: dict) -> None:
+        """
+        Update the Redis storage with the key-value pairs from another dictionary-like object.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            other (dict): A dictionary-like object containing the key-value pairs to update with.
+        """
+        pipeline = self.redis_stm_client._client.pipeline()
+        for key, value in other.items():
+            pipeline.hset(workflow_instance_id, key, pickle.dumps(value))
+        pipeline.execute()
+
+    def __len__(self, workflow_instance_id: str) -> int:
+        """
+        Get the number of items in the Redis storage for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            int: The number of items in the Redis storage.
+        """
+        return self.redis_stm_client._client.hlen(workflow_instance_id)
+
+
+if __name__ == "__main__":
+    # Create Redis client
+    redis_client = RedisConnector()
+
+    # Create RedisSTM instance
+    stm = RedisSTM(redis_stm_client=redis_client)
+
+    # Test workflow instance ID
+    workflow_id = "test_workflow"
+
+    # Get workflow instance proxy
+    workflow = stm(workflow_id)
+
+    # Test basic operations
+    print("Testing basic operations...")
+
+    # Set items
+    workflow["key1"] = "value1"
+    workflow["key2"] = {"nested": "value2"}
+
+    # Get items
+    print(f"Get key1: {workflow['key1']}")
+    print(f"Get key2: {workflow['key2']}")
+
+    # Test contains
+    print(f"Contains key1: {'key1' in workflow}")
+
+    # Test keys, values, items
+    print(f"Keys: {workflow.keys()}")
+    print(f"Values: {workflow.values()}")
+    print(f"Items: {workflow.items()}")
+
+    # Test length
+    print(f"Length: {len(workflow)}")
+
+    # Test pop
+    popped = workflow.pop("key1")
+    print(f"Popped value: {popped}")
+
+    # Test update
+    workflow.update({"key3": "value3", "key4": "value4"})
+    print(f"After update - keys: {workflow.keys()}")
+
+    # Test clear
+    workflow.clear()
+    print(f"After clear - length: {len(workflow)}")
diff --git a/omagent_core/memories/stms/stm_sharedMem.py b/omagent_core/memories/stms/stm_sharedMem.py
new file mode 100644
index 0000000000000000000000000000000000000000..e1d1164540accf096fb07abb37a4906bd067f5f3
--- /dev/null
+++ b/omagent_core/memories/stms/stm_sharedMem.py
@@ -0,0 +1,331 @@
+import hashlib
+import pickle
+from multiprocessing import shared_memory
+from typing import Any
+import atexit
+
+import numpy as np
+from omagent_core.memories.stms.stm_base import STMBase, WorkflowInstanceProxy
+from omagent_core.utils.registry import registry
+
+
+@registry.register_component()
+class SharedMemSTM(STMBase):
+    def __init__(self, id=None):
+        super().__init__()
+        self.id = id
+        self.workflow_instance_ids = set()
+        atexit.register(self.cleanup)
+
+    def __del__(self):
+        self.cleanup()
+
+    def cleanup(self):
+        for workflow_instance_id in list(self.workflow_instance_ids):
+            self.clear(workflow_instance_id)
+
+    def __call__(self, workflow_instance_id: str):
+        """
+        Return a WorkflowInstanceProxy for the given workflow instance ID.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            WorkflowInstanceProxy: A proxy object for accessing the workflow instance data.
+        """
+        self.workflow_instance_ids.add(workflow_instance_id)
+        return WorkflowInstanceProxy(self, workflow_instance_id)
+
+    def _create_shm(self, workflow_instance_id: str, size: int = 1024 * 1024 * 100):
+        """Create a new shared memory block"""
+        shortened_id = hashlib.md5(workflow_instance_id.encode()).hexdigest()[:8]
+        shm = shared_memory.SharedMemory(name=shortened_id, create=True, size=size)
+        return shm
+
+    def _get_shm(self, workflow_instance_id, size: int = 1024 * 1024 * 100):
+        # Hash the long workflow_instance_id to a shorter fixed-length string
+        if workflow_instance_id:
+            shortened_id = hashlib.md5(workflow_instance_id.encode()).hexdigest()[:8]
+        else:
+            shortened_id = "omagent_lite_ver."
+        try:
+            shm = shared_memory.SharedMemory(name=shortened_id)
+        except FileNotFoundError:
+            shm = shared_memory.SharedMemory(create=True, size=size, name=shortened_id)
+        return shm
+
+    def __getitem__(self, key: tuple | str) -> Any:
+        """
+        Retrieve an item or all items from shared memory.
+
+        Args:
+            key (tuple | str): Either a tuple containing (workflow_instance_id, key) or just workflow_instance_id
+
+        Returns:
+            Any: The value associated with the given key, or a dictionary of all key-value pairs.
+
+        Raises:
+            KeyError: If the key is not found.
+        """
+        if isinstance(key, tuple):
+            workflow_instance_id, key = key
+            shm = self._get_shm(workflow_instance_id)
+            try:
+                data = pickle.loads(bytes(shm.buf).strip(b"\x00"))
+                if key not in data:
+                    raise KeyError(key)
+                return data[key]
+            except (pickle.UnpicklingError, EOFError):
+                raise KeyError(key)
+            finally:
+                shm.close()
+        else:
+            workflow_instance_id = key
+            shm = self._get_shm(workflow_instance_id)
+            try:
+                return pickle.loads(bytes(shm.buf).strip(b"\x00"))
+            except (pickle.UnpicklingError, EOFError):
+                return {}
+            finally:
+                shm.close()
+
+    def __setitem__(self, key: tuple, value: Any) -> None:
+        """
+        Set an item in shared memory.
+
+        Args:
+            key (tuple): A tuple containing (workflow_instance_id, key)
+            value (Any): The value to associate with the key.
+        """
+        workflow_instance_id, key = key
+        shm = self._get_shm(workflow_instance_id)
+        try:
+            data = pickle.loads(bytes(shm.buf).strip(b"\x00"))
+        except (pickle.UnpicklingError, EOFError):
+            data = {}
+
+        data[key] = value
+        pickled_data = pickle.dumps(data)
+        shm.buf[: len(pickled_data)] = pickled_data
+        shm.close()
+
+    def __delitem__(self, workflow_instance_id: str, key: str) -> None:
+        """
+        Delete an item from shared memory.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to delete.
+
+        Raises:
+            KeyError: If the key is not found.
+        """
+        shm = self._get_shm(workflow_instance_id)
+        try:
+            data = pickle.loads(bytes(shm.buf).strip(b"\x00"))
+            if key not in data:
+                raise KeyError(key)
+            del data[key]
+            pickled_data = pickle.dumps(data)
+            shm.buf[:] = b"\x00" * len(shm.buf)  # Clear buffer
+            shm.buf[: len(pickled_data)] = pickled_data
+        except (pickle.UnpicklingError, EOFError):
+            raise KeyError(key)
+        finally:
+            shm.close()
+
+    def __contains__(self, key: tuple) -> bool:
+        """
+        Check if a key exists in shared memory.
+
+        Args:
+            key (tuple): A tuple containing (workflow_instance_id, key)
+
+        Returns:
+            bool: True if the key exists, False otherwise.
+        """
+        workflow_instance_id, key = key
+        try:
+            self[workflow_instance_id, key]
+            return True
+        except KeyError:
+            return False
+
+    def keys(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all keys in shared memory for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all keys.
+        """
+        try:
+            return list(self[workflow_instance_id].keys())
+        except KeyError:
+            return []
+
+    def values(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all values in shared memory for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all values.
+        """
+        try:
+            return list(self[workflow_instance_id].values())
+        except KeyError:
+            return []
+
+    def items(self, workflow_instance_id: str) -> list:
+        """
+        Get a list of all key-value pairs in shared memory for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            list: A list containing all key-value pairs.
+        """
+        try:
+            return list(self[workflow_instance_id].items())
+        except KeyError:
+            return []
+
+    def get(self, workflow_instance_id: str, key: str, default: Any = None) -> Any:
+        """
+        Get an item from shared memory.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to retrieve.
+            default (Any, optional): The default value to return if the key is not found.
+
+        Returns:
+            Any: The value associated with the given key, or the default value if not found.
+        """
+        try:
+            return self[workflow_instance_id, key]
+        except KeyError:
+            return default
+
+    def clear(self, workflow_instance_id: str) -> None:
+        """
+        Remove all items from shared memory for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+        """
+        try:
+            if workflow_instance_id:
+                shortened_id = hashlib.md5(workflow_instance_id.encode()).hexdigest()[:8]
+            else: 
+                shortened_id = "omagent_lite_ver."
+            shm = shared_memory.SharedMemory(name=shortened_id)
+            shm.buf[:] = b"\x00" * len(shm.buf)
+            shm.close()
+            shm.unlink()
+        except FileNotFoundError:
+            pass
+
+    def pop(self, workflow_instance_id: str, key: str, default: Any = None) -> Any:
+        """
+        Remove and return an item from shared memory.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            key (str): The key of the item to remove.
+            default (Any, optional): The default value to return if not found.
+
+        Returns:
+            Any: The value associated with the given key, or the default value if not found.
+        """
+        try:
+            value = self[workflow_instance_id, key]
+            self.__delitem__(workflow_instance_id, key)
+            return value
+        except KeyError:
+            return default
+
+    def update(self, workflow_instance_id: str, other: dict) -> None:
+        """
+        Update shared memory with key-value pairs from another dictionary.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+            other (dict): A dictionary containing the key-value pairs to update with.
+        """
+        for key, value in other.items():
+            self[workflow_instance_id, key] = value
+
+    def __len__(self, workflow_instance_id: str) -> int:
+        """
+        Get the number of items in shared memory for a workflow instance.
+
+        Args:
+            workflow_instance_id (str): The ID of the workflow instance.
+
+        Returns:
+            int: The number of items in shared memory.
+        """
+        try:
+            return len(self[workflow_instance_id])
+        except KeyError:
+            return 0
+
+
+if __name__ == "__main__":
+    # Create SharedMemSTM instance
+    stm = SharedMemSTM()
+
+    # Test workflow instance ID
+    workflow_id = "test_workflow"
+
+    # Get workflow instance proxy
+    workflow = stm(workflow_id)
+
+    # # Test basic operations
+    # print("Testing basic operations...")
+
+    # # Set items
+    # workflow["key1"] = "value1"
+    # workflow["key2"] = {"nested": "value2"}
+
+    # # Get items
+    # print(f"Get key1: {workflow['key1']}")
+    # print(f"Get key2: {workflow['key2']}")
+
+    # # Test contains
+    # print(f"Contains key1: {'key1' in workflow}")
+
+    # # Test keys, values, items
+    # print(f"Keys: {workflow.keys()}")
+    # print(f"Values: {workflow.values()}")
+    # print(f"Items: {workflow.items()}")
+
+    # # Test length
+    # print(f"Length: {len(workflow)}")
+
+    # # Test pop
+    # popped = workflow.pop("key1")
+    # print(f"Popped value: {popped}")
+
+    # # Test update
+    # workflow.update({"key3": "value3", "key4": "value4"})
+    # print(f"After update - keys: {workflow.keys()}")
+
+    # # Test clear
+    # workflow.clear()
+    # print(f"After clear - length: {len(workflow)}")
+
+    workflow["key1"] = {}
+    print(workflow["key1"])
+    x = workflow["key1"]
+    x["a"] = 1
+    workflow["key1"] = x
+    print(workflow["key1"])
diff --git a/omagent_core/models/asr/stt.py b/omagent_core/models/asr/stt.py
new file mode 100644
index 0000000000000000000000000000000000000000..a0a22a9e6a87655ddcd7c35386f8fc7a496a32bb
--- /dev/null
+++ b/omagent_core/models/asr/stt.py
@@ -0,0 +1,55 @@
+import io
+from typing import Any, Optional
+
+from openai import NOT_GIVEN, AsyncOpenAI, OpenAI
+from pydub import AudioSegment
+
+from ...base import BotBase
+
+
+class STT(BotBase):
+    model_id: str = "whisper-1"
+    endpoint: Optional[str] = None
+    api_key: str
+    language: Optional[str] = None
+    response_format: str = "verbose_json"
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        protected_namespaces = ()
+
+    def __init__(self, /, **data: Any) -> None:
+        super().__init__(**data)
+        self.client = OpenAI(base_url=self.endpoint, api_key=self.api_key)
+        self.aclient = AsyncOpenAI(base_url=self.endpoint, api_key=self.api_key)
+
+    def _as2bytes(self, audio: AudioSegment) -> io.BytesIO:
+        audio_bytes = io.BytesIO()
+        audio.export(audio_bytes, format="mp3")
+        audio_bytes.seek(0)
+        audio_bytes.name = "buffer.mp3"
+        return audio_bytes
+
+    def infer(self, audio: AudioSegment) -> dict:
+        audio_bytes = self._as2bytes(audio)
+
+        trans = self.client.audio.transcriptions.create(
+            model=self.model_id,
+            file=audio_bytes,
+            response_format=self.response_format,
+            language=NOT_GIVEN if self.language is None else self.language,
+        )
+        return trans.to_dict()
+
+    async def ainfer(self, audio: AudioSegment) -> dict:
+        audio_bytes = self._as2bytes(audio)
+
+        trans = await self.aclient.audio.transcriptions.create(
+            model=self.model_id,
+            file=audio_bytes,
+            response_format=self.response_format,
+            language=NOT_GIVEN if self.language is None else self.language,
+        )
+        return trans.to_dict()
diff --git a/omagent_core/models/encoders/__init__.py b/omagent_core/models/encoders/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/models/encoders/base.py b/omagent_core/models/encoders/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..2c386bfb661ece3a6516d30dbae5fa21171016ee
--- /dev/null
+++ b/omagent_core/models/encoders/base.py
@@ -0,0 +1,31 @@
+from abc import ABC, abstractmethod
+from typing import List
+
+from ...base import BotBase
+from ...utils.general import chunks
+
+
+class EncoderBase(BotBase, ABC):
+    endpoint: str
+    dim: int  # Dimension of the vector
+    batch_size: int = 20
+
+    @abstractmethod
+    def _infer(self, data: List[str], **kwargs) -> List[List[float]]:
+        """Encoding"""
+
+    async def _ainfer(self, data: List[str], **kwargs) -> List[List[float]]:
+        """Async encoding"""
+        raise NotImplementedError("Async generation not implemented for this Encoder.")
+
+    def infer(self, data: List[str], **kwargs) -> List[List[float]]:
+        res = []
+        for chunk in chunks(data, self.batch_size, self.batch_size):
+            res += self._infer(chunk, **kwargs)
+        return res
+
+    async def ainfer(self, data: List[str], **kwargs) -> List[List[float]]:
+        res = []
+        for chunk in chunks(data, self.batch_size, self.batch_size):
+            res += await self._ainfer(chunk, **kwargs)
+        return res
diff --git a/omagent_core/models/encoders/openai_encoder.py b/omagent_core/models/encoders/openai_encoder.py
new file mode 100644
index 0000000000000000000000000000000000000000..c8a5b8e7dc2440b4cab8382d8df2f70a8821bfa3
--- /dev/null
+++ b/omagent_core/models/encoders/openai_encoder.py
@@ -0,0 +1,32 @@
+from typing import Any, List
+
+from openai import AsyncOpenAI, OpenAI
+
+from ...utils.registry import registry
+from .base import EncoderBase
+
+
+@registry.register_encoder()
+class OpenaiTextEmbeddingV3(EncoderBase):
+    model_id: str = "text-embedding-3-large"
+    api_key: str
+    dim: int = 3072
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        protected_namespaces = ()
+        extra = "allow"
+
+    def __init__(self, /, **data: Any) -> None:
+        super().__init__(**data)
+        self.client = OpenAI(base_url=self.endpoint, api_key=self.api_key)
+        self.aclient = AsyncOpenAI(base_url=self.endpoint, api_key=self.api_key)
+
+    def _infer(self, data: List[str], **kwargs) -> List[List[float]]:
+        res = self.client.embeddings.create(input=data, model=self.model_id)
+        return [item.embedding for item in res.data]
+
+    async def _ainfer(self, data: List[str], **kwargs) -> List[List[float]]:
+        res = await self.aclient.embeddings.create(input=data, model=self.model_id)
+        return [item.embedding for item in res.data]
diff --git a/omagent_core/models/llms/__init__.py b/omagent_core/models/llms/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/models/llms/azure_gpt.py b/omagent_core/models/llms/azure_gpt.py
new file mode 100644
index 0000000000000000000000000000000000000000..6201b1ba9a5807961505e9d133e2fcfab0a4d3bc
--- /dev/null
+++ b/omagent_core/models/llms/azure_gpt.py
@@ -0,0 +1,187 @@
+import os
+import sysconfig
+from datetime import datetime
+from typing import Any, Dict, List
+
+import geocoder
+from openai import AsyncAzureOpenAI, AzureOpenAI
+
+from ...utils.general import encode_image
+from ...utils.registry import registry
+from .base import BaseLLM
+from .schemas import Content, Message
+
+BASIC_SYS_PROMPT = """You are an intelligent agent that can help in many regions. 
+Flowing are some basic information about your working environment, please try your best to answer the questions based on them if needed. 
+Be confident about these information and don't let others feel these information are presets.
+Be concise.
+---BASIC IMFORMATION---
+Current Datetime: {}
+Region: {}
+Operating System: {}"""
+
+
+@registry.register_llm()
+class AzureGPTLLM(BaseLLM):
+    model_id: str
+    vision: bool = False
+    endpoint: str
+    api_key: str
+    api_version: str = "2024-02-15-preview"
+    temperature: float = 1.0
+    max_tokens: int = 2048
+    use_default_sys_prompt: bool = True
+    response_format: str = "text"
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        protected_namespaces = ()
+        extra = "allow"
+
+    def __init__(self, /, **data: Any) -> None:
+        super().__init__(**data)
+        self.client = AzureOpenAI(
+            api_key=self.api_key,
+            azure_endpoint=self.endpoint,
+            api_version=self.api_version,
+        )
+        self.aclient = AsyncAzureOpenAI(
+            api_key=self.api_key,
+            azure_endpoint=self.endpoint,
+            api_version=self.api_version,
+        )
+
+    def _call(self, records: List[Message], **kwargs) -> Dict:
+        if self.api_key is None or self.api_key == "":
+            raise ValueError("api_key is required")
+
+        body = self._msg2req(records)
+        if kwargs.get("tool_choice"):
+            body["tool_choice"] = kwargs["tool_choice"]
+        if kwargs.get("tools"):
+            body["tools"] = kwargs["tools"]
+
+        if self.vision:
+            res = self.client.chat.completions.create(
+                model=self.model_id,
+                messages=body["messages"],
+                temperature=self.temperature,
+                max_tokens=self.max_tokens,
+            )
+        else:
+            res = self.client.chat.completions.create(
+                model=self.model_id,
+                messages=body["messages"],
+                temperature=self.temperature,
+                max_tokens=self.max_tokens,
+                response_format=body.get("response_format", None),
+                tools=body.get("tools", None),
+            )
+        res = res.model_dump()
+        body.update({"response": res})
+        return res
+
+    async def _acall(self, records: List[Message], **kwargs) -> Dict:
+        if self.api_key is None or self.api_key == "":
+            raise ValueError("api_key is required")
+
+        body = self._msg2req(records)
+        if kwargs.get("tool_choice"):
+            body["tool_choice"] = kwargs["tool_choice"]
+        if kwargs.get("tools"):
+            body["tools"] = kwargs["tools"]
+
+        if self.vision:
+            res = await self.aclient.chat.completions.create(
+                model=self.model_id,
+                messages=body["messages"],
+                temperature=self.temperature,
+                max_tokens=self.max_tokens,
+            )
+        else:
+            res = await self.aclient.chat.completions.create(
+                model=self.model_id,
+                messages=body["messages"],
+                temperature=self.temperature,
+                max_tokens=self.max_tokens,
+                response_format=body.get("response_format", None),
+                tools=body.get("tools", None),
+            )
+        res = res.model_dump()
+        body.update({"response": res})
+        return res
+
+    def _msg2req(self, records: List[Message]) -> dict:
+        def get_content(msg: List[Content] | Content) -> List[dict] | str:
+            if isinstance(msg, list):
+                return [c.model_dump(exclude_none=True) for c in msg]
+            elif isinstance(msg, Content) and msg.type == "text":
+                return msg.text
+            else:
+                raise ValueError("Invalid message type")
+
+        messages = [
+            {"role": message.role, "content": get_content(message.content)}
+            for message in records
+        ]
+        if self.vision:
+            processed_messages = []
+            for message in messages:
+                if message["role"] == "user":
+                    if isinstance(message["content"], str):
+                        message["content"] = [
+                            {"type": "text", "text": message["content"]}
+                        ]
+            merged_dict = {}
+            for message in messages:
+                if message["role"] == "user":
+                    merged_dict["role"] = message["role"]
+                    if "content" in merged_dict:
+                        merged_dict["content"] += message["content"]
+                    else:
+                        merged_dict["content"] = message["content"]
+                else:
+                    processed_messages.append(message)
+            processed_messages.append(merged_dict)
+            messages = processed_messages
+        if self.use_default_sys_prompt:
+            messages = [self._generate_default_sys_prompt()] + messages
+        body = {
+            "model": self.model_id,
+            "messages": messages,
+            "temperature": self.temperature,
+            "max_tokens": self.max_tokens,
+        }
+        if self.response_format != "text":
+            body["response_format"] = {"type": self.response_format}
+        return body
+
+    def _generate_default_sys_prompt(self) -> Dict:
+        loc = self._get_location()
+        os = self._get_linux_distribution()
+        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        promt_str = BASIC_SYS_PROMPT.format(loc, os, current_time)
+        return {"role": "system", "content": promt_str}
+
+    def _get_linux_distribution(self) -> str:
+        platform = sysconfig.get_platform()
+        if "linux" in platform:
+            if os.path.exists("/etc/lsb-release"):
+                with open("/etc/lsb-release", "r") as f:
+                    for line in f:
+                        if line.startswith("DISTRIB_DESCRIPTION="):
+                            return line.split("=")[1].strip()
+            elif os.path.exists("/etc/os-release"):
+                with open("/etc/os-release", "r") as f:
+                    for line in f:
+                        if line.startswith("PRETTY_NAME="):
+                            return line.split("=")[1].strip()
+        return platform
+
+    def _get_location(self) -> str:
+        g = geocoder.ip("me")
+        if g.ok:
+            return g.city + "," + g.country
+        else:
+            return "unknown"
diff --git a/omagent_core/models/llms/base.py b/omagent_core/models/llms/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..af200d21f5ed5540395ca368fb47c57ae1472b67
--- /dev/null
+++ b/omagent_core/models/llms/base.py
@@ -0,0 +1,258 @@
+from __future__ import annotations
+
+import re
+from abc import ABC, abstractmethod
+from collections import defaultdict
+from collections.abc import Hashable
+from pathlib import Path
+from typing import Any, ClassVar, Dict, List, Optional, TypeVar, Union
+
+from PIL import Image
+from pydantic import Field, field_validator
+from tenacity import retry, stop_after_attempt, stop_after_delay
+
+from ...base import BotBase
+from ...utils.env import EnvVar
+from ...utils.general import LRUCache
+from ...utils.registry import registry
+from .prompt.base import _OUTPUT_PARSER, StrParser
+from .prompt.parser import BaseOutputParser
+from .prompt.prompt import PromptTemplate
+from .schemas import Message
+import copy
+from collections.abc import Iterator
+
+T = TypeVar("T", str, dict, list)
+
+
+class BaseLLM(BotBase, ABC):
+    cache: bool = False
+    lru_cache: LRUCache = Field(default=LRUCache(EnvVar.LLM_CACHE_NUM))
+
+    @property
+    def workflow_instance_id(self) -> str:
+        if hasattr(self, "_parent"):
+            return self._parent.workflow_instance_id
+        return None
+
+    @workflow_instance_id.setter
+    def workflow_instance_id(self, value: str):
+        if hasattr(self, "_parent"):
+            self._parent.workflow_instance_id = value
+
+    @abstractmethod
+    def _call(self, records: List[Message], **kwargs) -> str:
+        """Run the LLM on the given prompt and input."""
+
+    async def _acall(self, records: List[Message], **kwargs) -> str:
+        """Run the LLM on the given prompt and input."""
+        raise NotImplementedError("Async generation not implemented for this LLM.")
+
+    def generate(self, records: List[Message], **kwargs) -> str: # TODO: use python native lru cache
+        """Run the LLM on the given prompt and input."""
+        if self.cache:
+            key = self._cache_key(records)
+            cached_res = self.lru_cache.get(key)
+            if cached_res:
+                return cached_res
+            else:
+                gen = self._call(records, **kwargs)
+                self.lru_cache.put(key, gen)
+                return gen
+        else:
+            return self._call(records, **kwargs)
+
+    @retry(
+        stop=(
+            stop_after_delay(EnvVar.STOP_AFTER_DELAY)
+            | stop_after_attempt(EnvVar.STOP_AFTER_ATTEMPT)
+        ),
+        reraise=True,
+    )
+    async def agenerate(self, records: List[str], **kwargs) -> str:
+        """Run the LLM on the given prompt and input."""
+        if self.cache:
+            key = self._cache_key(records)
+            cached_res = self.lru_cache.get(key)
+            if cached_res:
+                return cached_res
+            else:
+                gen = await self._acall(records, **kwargs)
+                self.lru_cache.put(key, gen)
+                return gen
+        else:
+            return await self._acall(records, **kwargs)
+
+    def _cache_key(self, records: List[Message]) -> int:
+        return str([item.model_dump() for item in records])
+
+    def dict(self, *args, **kwargs):
+        kwargs["exclude"] = {"lru_cache"}
+        return super().model_dump(*args, **kwargs)
+
+    def json(self, *args, **kwargs):
+        kwargs["exclude"] = {"lru_cache"}
+        return super().model_dump_json(*args, **kwargs)
+
+
+T = TypeVar("T", str, dict, list)
+
+
+class BaseLLMBackend(BotBase, ABC):
+    """Prompts prepare and LLM infer"""
+
+    output_parser: Optional[BaseOutputParser] = None
+    prompts: List[PromptTemplate] = []
+    llm: BaseLLM
+
+
+    @property
+    def token_usage(self):
+        if not hasattr(self, 'workflow_instance_id'):
+            raise AttributeError("workflow_instance_id not set")
+        return dict(self.stm(self.workflow_instance_id).get('token_usage', defaultdict(int)))
+    
+    @field_validator("output_parser", mode="before")
+    @classmethod
+    def set_output_parser(cls, output_parser: Union[BaseOutputParser, Dict, None]):
+        if output_parser is None:
+            return StrParser()
+        elif isinstance(output_parser, BaseOutputParser):
+            return output_parser
+        elif isinstance(output_parser, dict):
+            return _OUTPUT_PARSER[output_parser["name"]](**output_parser)
+        else:
+            raise ValueError
+
+    @field_validator("prompts", mode="before")
+    @classmethod
+    def set_prompts(
+        cls, prompts: List[Union[PromptTemplate, Dict, str]]
+    ) -> List[PromptTemplate]:
+        init_prompts = []
+        for prompt in prompts:
+            prompt = copy.deepcopy(prompt)
+            if isinstance(prompt, Path):
+                if prompt.suffix == ".prompt":
+                    init_prompts.append(PromptTemplate.from_file(prompt))
+            elif isinstance(prompt, str):
+                if prompt.endswith(".prompt"):
+                    init_prompts.append(PromptTemplate.from_file(prompt))
+                init_prompts.append(PromptTemplate.from_template(prompt))
+            elif isinstance(prompt, dict):
+                init_prompts.append(PromptTemplate.from_config(prompt))
+            elif isinstance(prompt, PromptTemplate):
+                init_prompts.append(prompt)
+            else:
+                raise ValueError(
+                    "Prompt only support str, dict and PromptTemplate object"
+                )
+        return init_prompts
+
+    @field_validator("llm", mode="before")
+    @classmethod
+    def set_llm(cls, llm: Union[BaseLLM, Dict]):
+        if isinstance(llm, dict):
+            return registry.get_llm(llm["name"])(**llm)
+        elif isinstance(llm, BaseLLM):
+            return llm
+        else:
+            raise ValueError("LLM only support dict and BaseLLM object")
+
+    def prep_prompt(
+        self, input_list: List[Dict[str, Any]], prompts=None, **kwargs
+    ) -> List[List[Message]]:
+        """Prepare prompts from inputs."""
+        if prompts is None:
+            prompts = self.prompts
+        images = []
+        if len(kwargs_images := kwargs.get("images", [])):
+            images = kwargs_images
+        processed_prompts = []
+        for inputs in input_list:
+            records = []
+            for prompt in prompts:
+                selected_inputs = {k: inputs.get(k, "") for k in prompt.input_variables}
+                prompt_str = prompt.template
+                parts = re.split(r"(\{\{.*?\}\})", prompt_str)
+                formatted_parts = []
+                for part in parts:
+                    if part.startswith("{{") and part.endswith("}}"):
+                        part = part[2:-2].strip()
+                        value = selected_inputs[part]
+                        if isinstance(value, (Image.Image, list)):
+                            formatted_parts.extend(
+                                [value] if isinstance(value, Image.Image) else value
+                            )
+                        else:
+                            formatted_parts.append(str(value))
+                    else:
+                        formatted_parts.append(str(part))
+                formatted_parts = (
+                    formatted_parts[0] if len(formatted_parts) == 1 else formatted_parts
+                )
+                if prompt.role == "system":
+                    records.append(Message.system(formatted_parts))
+                elif prompt.role == "user":
+                    records.append(Message.user(formatted_parts))
+            if len(images):
+                records.append(Message.user(images))
+            processed_prompts.append(records)
+        return processed_prompts
+
+    def infer(self, input_list: List[Dict[str, Any]], **kwargs) -> List[T]:
+        prompts = self.prep_prompt(input_list, **kwargs)
+        res = []
+        stm_token_usage = self.stm(self.workflow_instance_id).get('token_usage', defaultdict(int))
+        
+        def process_stream(self, stream_output):
+            for chunk in stream_output:
+                if chunk.usage is not None:
+                    for key, value in chunk.usage.dict().items():
+                        if key in ["prompt_tokens", "completion_tokens", 'total_tokens']:
+                            if value is not None:
+                                stm_token_usage[key] += value
+                    self.stm(self.workflow_instance_id)['token_usage'] = stm_token_usage
+            
+                yield chunk
+        
+        for prompt in prompts:
+            output = self.llm.generate(prompt, **kwargs)
+            if not isinstance(output, Iterator):
+                for key, value in output.get("usage", {}).items():
+                    if key in ["prompt_tokens", "completion_tokens", 'total_tokens']:
+                        if value is not None:
+                            stm_token_usage[key] += value
+                if not self.llm.stream:
+                    for choice in output["choices"]:
+                        if choice.get("message"):
+                            choice["message"]["content"] = self.output_parser.parse(
+                                choice["message"]["content"]
+                            )
+                res.append(output)
+            else:
+                res.append(process_stream(self, output))
+                
+        self.stm(self.workflow_instance_id)['token_usage'] = stm_token_usage
+        return res
+
+    async def ainfer(self, input_list: List[Dict[str, Any]], **kwargs) -> List[T]:
+        prompts = self.prep_prompt(input_list)
+        res = []
+        for prompt in prompts:
+            output = await self.llm.agenerate(prompt, **kwargs)
+            for key, value in output["usage"].items():
+                self.token_usage[key] += value
+            for choice in output["choices"]:
+                if choice.get("message"):
+                    choice["message"]["content"] = self.output_parser.parse(
+                        choice["message"]["content"]
+                    )
+            res.append(output)
+        return res
+
+    def simple_infer(self, **kwargs: Any) -> T:
+        return self.infer([kwargs])[0]
+
+    async def simple_ainfer(self, **kwargs: Any) -> T:
+        return await self.ainfer([kwargs])[0]
diff --git a/omagent_core/models/llms/openai_gpt.py b/omagent_core/models/llms/openai_gpt.py
new file mode 100644
index 0000000000000000000000000000000000000000..46b1bb89db6f61670d12f60a935919f0234bc222
--- /dev/null
+++ b/omagent_core/models/llms/openai_gpt.py
@@ -0,0 +1,269 @@
+import os
+import sysconfig
+from datetime import datetime
+from typing import Any, Dict, List, Union, Optional
+
+import geocoder
+from openai import AsyncOpenAI, OpenAI
+from pydantic import Field
+
+from omagent_core.utils.registry import registry
+from omagent_core.models.llms.base import BaseLLM
+from omagent_core.models.llms.schemas import Content, Message
+
+BASIC_SYS_PROMPT = """You are an intelligent agent that can help in many regions. 
+Flowing are some basic information about your working environment, please try your best to answer the questions based on them if needed. 
+Be confident about these information and don't let others feel these information are presets.
+Be concise.
+---BASIC INFORMATION---
+Current Datetime: {}
+Region: {}
+Operating System: {}"""
+
+
+@registry.register_llm()
+class OpenaiGPTLLM(BaseLLM):
+    model_id: str = Field(
+        default=os.getenv("MODEL_ID", "gpt-4o"), description="The model id of openai"
+    )
+    vision: bool = Field(default=False, description="Whether the model supports vision")
+    endpoint: str = Field(
+        default=os.getenv("ENDPOINT", "https://api.openai.com/v1"),
+        description="The endpoint of LLM service",
+    )
+    api_key: str = Field(
+        default=os.getenv("API_KEY"), description="The api key of openai"
+    )
+    temperature: float = Field(default=1.0, description="The temperature of LLM")
+    top_p: float = Field(
+        default=1.0,
+        description="The top p of LLM, controls diversity of responses. Should not be used together with temperature - use either temperature or top_p but not both",
+    )
+    stream: bool = Field(default=False, description="Whether to stream the response")
+    max_tokens: int = Field(default=2048, description="The max tokens of LLM")
+    use_default_sys_prompt: bool = Field(
+        default=True, description="Whether to use the default system prompt"
+    )
+    response_format: Optional[Union[dict, str]] = Field(default='text', description="The response format of openai")
+    n: int = Field(default=1, description="The number of responses to generate")
+    frequency_penalty: float = Field(
+        default=0, description="The frequency penalty of LLM, -2 to 2"
+    )
+    logit_bias: Optional[dict] = Field(
+        default=None, description="The logit bias of LLM"
+    )
+    logprobs: bool = Field(default=False, description="The logprobs of LLM")
+    top_logprobs: Optional[int] = Field(
+        default=None,
+        description="The top logprobs of LLM, logprobs must be set to true if this parameter is used",
+    )
+    stop: Union[str, List[str], None] = Field(
+        default=None,
+        description="Specifies stop sequences that will halt text generation, can be string or list of strings",
+    )
+    stream_options: Optional[dict] = Field(
+        default=None,
+        description="Configuration options for streaming responses when stream=True",
+    )
+    tools: Optional[List[dict]] = Field(
+        default=None,
+        description="A list of function tools (max 128) that the model can call, each requiring a type, name and optional description/parameters defined in JSON Schema format.",
+    )
+    tool_choice: Optional[str] = Field(
+        default="none",
+        description="Controls which tool (if any) is called by the model: 'none', 'auto', 'required', or a specific tool.",
+    )
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        protected_namespaces = ()
+        extra = "allow"
+
+    def check_response_format(self) -> Optional[dict]:
+        if isinstance(self.response_format, str):
+            if self.response_format == "text":
+                self.response_format = {"type": "text"}
+            elif self.response_format == "json_object":
+                self.response_format = {"type": "json_object"}
+        elif isinstance(self.response_format, dict):
+            for key, value in self.response_format.items():
+                if key not in ["type", "json_schema"]:
+                    raise ValueError(f"Invalid response format key: {key}")
+                if key == "type":
+                    if value not in ["text", "json_object"]:
+                        raise ValueError(f"Invalid response format value: {value}")
+                elif key == "json_schema":
+                    if not isinstance(value, dict):
+                        raise ValueError(f"Invalid response format value: {value}")
+        else:
+            raise ValueError(f"Invalid response format: {self.response_format}")
+
+    def model_post_init(self, __context: Any) -> None:
+        self.check_response_format()
+        self.client = OpenAI(api_key=self.api_key, base_url=self.endpoint)
+        self.aclient = AsyncOpenAI(api_key=self.api_key, base_url=self.endpoint)
+
+    def _call(self, records: List[Message], **kwargs) -> Dict:
+        if self.api_key is None or self.api_key == "":
+            raise ValueError("api_key is required")
+
+        messages = self._msg2req(records)
+
+        if self.vision:
+            res = self.client.chat.completions.create(
+                model=self.model_id,
+                messages=messages,
+                temperature=kwargs.get("temperature", self.temperature),
+                max_tokens=kwargs.get("max_tokens", self.max_tokens),
+                stream=kwargs.get("stream", self.stream),
+                n=kwargs.get("n", self.n),
+                top_p=kwargs.get("top_p", self.top_p),
+                frequency_penalty=kwargs.get(
+                    "frequency_penalty", self.frequency_penalty
+                ),
+                logit_bias=kwargs.get("logit_bias", self.logit_bias),
+                logprobs=kwargs.get("logprobs", self.logprobs),
+                top_logprobs=kwargs.get("top_logprobs", self.top_logprobs),
+                stop=kwargs.get("stop", self.stop),
+                stream_options=kwargs.get("stream_options", self.stream_options),
+            )
+        else:
+            res = self.client.chat.completions.create(
+                model=self.model_id,
+                messages=messages,
+                temperature=kwargs.get("temperature", self.temperature),
+                max_tokens=kwargs.get("max_tokens", self.max_tokens),
+                response_format=kwargs.get("response_format", self.response_format),
+                tools=kwargs.get("tools", None),
+                tool_choice=kwargs.get("tool_choice", None),
+                stream=kwargs.get("stream", self.stream),
+                n=kwargs.get("n", self.n),
+                top_p=kwargs.get("top_p", self.top_p),
+                frequency_penalty=kwargs.get(
+                    "frequency_penalty", self.frequency_penalty
+                ),
+                logit_bias=kwargs.get("logit_bias", self.logit_bias),
+                logprobs=kwargs.get("logprobs", self.logprobs),
+                top_logprobs=kwargs.get("top_logprobs", self.top_logprobs),
+                stop=kwargs.get("stop", self.stop),
+                stream_options=kwargs.get("stream_options", self.stream_options),
+            )
+
+        if kwargs.get("stream", self.stream):
+            return res
+        else:
+            return res.model_dump()
+
+    async def _acall(self, records: List[Message], **kwargs) -> Dict:
+        if self.api_key is None or self.api_key == "":
+            raise ValueError("api_key is required")
+
+        messages = self._msg2req(records)
+
+        if self.vision:
+            res = await self.aclient.chat.completions.create(
+                model=self.model_id,
+                messages=messages,
+                temperature=kwargs.get("temperature", self.temperature),
+                max_tokens=kwargs.get("max_tokens", self.max_tokens),
+                n=kwargs.get("n", self.n),
+                top_p=kwargs.get("top_p", self.top_p),
+                frequency_penalty=kwargs.get(
+                    "frequency_penalty", self.frequency_penalty
+                ),
+                logit_bias=kwargs.get("logit_bias", self.logit_bias),
+                logprobs=kwargs.get("logprobs", self.logprobs),
+                top_logprobs=kwargs.get("top_logprobs", self.top_logprobs),
+                stop=kwargs.get("stop", self.stop),
+                stream_options=kwargs.get("stream_options", self.stream_options),
+            )
+        else:
+            res = await self.aclient.chat.completions.create(
+                model=self.model_id,
+                messages=messages,
+                temperature=kwargs.get("temperature", self.temperature),
+                max_tokens=kwargs.get("max_tokens", self.max_tokens),
+                response_format=kwargs.get("response_format", self.response_format),
+                tools=kwargs.get("tools", None),
+                n=kwargs.get("n", self.n),
+                top_p=kwargs.get("top_p", self.top_p),
+                frequency_penalty=kwargs.get(
+                    "frequency_penalty", self.frequency_penalty
+                ),
+                logit_bias=kwargs.get("logit_bias", self.logit_bias),
+                logprobs=kwargs.get("logprobs", self.logprobs),
+                top_logprobs=kwargs.get("top_logprobs", self.top_logprobs),
+                stop=kwargs.get("stop", self.stop),
+                stream_options=kwargs.get("stream_options", self.stream_options),
+            )
+        return res.model_dump()
+
+    def _msg2req(self, records: List[Message]) -> dict:
+        def get_content(msg: List[Content] | Content) -> List[dict] | str:
+            if isinstance(msg, list):
+                return [c.model_dump(exclude_none=True) for c in msg]
+            elif isinstance(msg, Content) and msg.type == "text":
+                return msg.text
+            elif isinstance(msg, Content) and msg.type == "image_url":
+                return [msg.model_dump(exclude_none=True)]
+            else:
+                print(f'msg: {msg}')
+                raise ValueError("Invalid message type")
+
+        messages = [
+            {"role": message.role, "content": get_content(message.content)}
+            for message in records
+        ]
+        if self.vision:
+            processed_messages = []
+            for message in messages:
+                if message["role"] == "user":
+                    if isinstance(message["content"], str):
+                        message["content"] = [
+                            {"type": "text", "text": message["content"]}
+                        ]
+            merged_dict = {}
+            for message in messages:
+                if message["role"] == "user":
+                    merged_dict["role"] = message["role"]
+                    if "content" in merged_dict:
+                        merged_dict["content"] += message["content"]
+                    else:
+                        merged_dict["content"] = message["content"]
+                else:
+                    processed_messages.append(message)
+            processed_messages.append(merged_dict)
+            messages = processed_messages
+        if self.use_default_sys_prompt:
+            messages = [self._generate_default_sys_prompt()] + messages
+        return messages
+
+    def _generate_default_sys_prompt(self) -> Dict:
+        loc = self._get_location()
+        os = self._get_linux_distribution()
+        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        promt_str = BASIC_SYS_PROMPT.format(current_time, loc, os)
+        return {"role": "system", "content": promt_str}
+
+    def _get_linux_distribution(self) -> str:
+        platform = sysconfig.get_platform()
+        if "linux" in platform:
+            if os.path.exists("/etc/lsb-release"):
+                with open("/etc/lsb-release", "r") as f:
+                    for line in f:
+                        if line.startswith("DISTRIB_DESCRIPTION="):
+                            return line.split("=")[1].strip()
+            elif os.path.exists("/etc/os-release"):
+                with open("/etc/os-release", "r") as f:
+                    for line in f:
+                        if line.startswith("PRETTY_NAME="):
+                            return line.split("=")[1].strip()
+        return platform
+
+    def _get_location(self) -> str:
+        g = geocoder.ip("me")
+        if g.ok:
+            return g.city + "," + g.country
+        else:
+            return "unknown"
diff --git a/omagent_core/models/llms/prompt/__init__.py b/omagent_core/models/llms/prompt/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..553e745c5c2661c6a693099d6e6c568302a197d4
--- /dev/null
+++ b/omagent_core/models/llms/prompt/__init__.py
@@ -0,0 +1,5 @@
+from .prompt import PromptTemplate
+
+__all__ = [
+    "PromptTemplate",
+]
diff --git a/omagent_core/models/llms/prompt/base.py b/omagent_core/models/llms/prompt/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..e24f4685a5302518f5c08e32a3b4a975c3c7cbd3
--- /dev/null
+++ b/omagent_core/models/llms/prompt/base.py
@@ -0,0 +1,158 @@
+from __future__ import annotations
+
+import sys
+
+sys.path.append("..")
+
+import json
+from abc import ABC, abstractmethod
+from pathlib import Path
+from typing import Any, Callable, Dict, List, Mapping, Optional, Set, Union
+
+import yaml
+from pydantic import Field
+
+from ....base import BotBase
+from .formatter import FStringFormatter, JinjiaFormatter
+from .parser import BaseOutputParser, DictParser, ListParser, StrParser
+
+DEFAULT_FORMATTER_MAPPING: Dict[str, Callable] = {
+    "f-string": FStringFormatter(),
+    "jinja2": JinjiaFormatter(),
+}
+
+_OUTPUT_PARSER = {
+    "StrParser": StrParser,
+    "ListParser": ListParser,
+    "DictParser": DictParser,
+}
+
+
+def check_valid_template(
+    template: str, template_format: str, input_variables: List[str]
+) -> None:
+    """Check that template string is valid."""
+    if template_format not in DEFAULT_FORMATTER_MAPPING:
+        valid_formats = list(DEFAULT_FORMATTER_MAPPING)
+        raise ValueError(
+            f"Invalid template format. Got `{template_format}`;"
+            f" should be one of {valid_formats}"
+        )
+    try:
+        formatter = DEFAULT_FORMATTER_MAPPING[template_format]
+        formatter.validate(template, input_variables)
+    except KeyError as e:
+        raise ValueError(
+            "Invalid prompt schema; check for mismatched or missing input parameters. "
+            + str(e)
+        )
+
+
+def _get_jinja2_variables_from_template(template: str) -> Set[str]:
+    try:
+        from jinja2 import Environment, meta
+    except ImportError:
+        raise ImportError(
+            "jinja2 not installed, which is needed to use the jinja2_formatter. "
+            "Please install it with `pip install jinja2`."
+        )
+    env = Environment()
+    ast = env.parse(template)
+    variables = meta.find_undeclared_variables(ast)
+    return variables
+
+
+class BasePromptTemplate(BotBase, ABC):
+    """Base class for all prompt templates, returning a prompt."""
+
+    input_variables: List[str]
+    """A list of the names of the variables the prompt template expects."""
+    output_parser: Optional[BaseOutputParser] = None
+    """How to parse the output of calling an LLM on this formatted prompt."""
+    partial_variables: Mapping[str, Union[str, Callable[[], str]]] = Field(
+        default_factory=dict
+    )
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "forbid"
+        arbitrary_types_allowed = True
+
+    def partial(self, **kwargs: Union[str, Callable[[], str]]) -> BasePromptTemplate:
+        """Return a partial of the prompt template."""
+        prompt_dict = self.__dict__.copy()
+        prompt_dict["input_variables"] = list(
+            set(self.input_variables).difference(kwargs)
+        )
+        prompt_dict["partial_variables"] = {**self.partial_variables, **kwargs}
+        return type(self)(**prompt_dict)
+
+    def _merge_partial_and_user_variables(self, **kwargs: Any) -> Dict[str, Any]:
+        # Get partial params:
+        partial_kwargs = {
+            k: v if isinstance(v, str) else v()
+            for k, v in self.partial_variables.items()
+        }
+        return {**partial_kwargs, **kwargs}
+
+    @abstractmethod
+    def format(self, **kwargs: Any) -> str:
+        """Format the prompt with the inputs.
+
+        Args:
+            kwargs: Any arguments to be passed to the prompt template.
+
+        Returns:
+            A formatted string.
+
+        Example:
+
+        .. code-block:: python
+
+            prompt.format(variable1="foo")
+        """
+
+    def save(self, file_path: Union[Path, str]) -> None:
+        """Save the prompt.
+
+        Args:
+            file_path: Path to directory to save prompt to.
+
+        Example:
+        .. code-block:: python
+
+            prompt.save(file_path="path/prompt.yaml")
+        """
+        if self.partial_variables:
+            raise ValueError("Cannot save prompt with partial variables.")
+        # Convert file to Path object.
+        if isinstance(file_path, str):
+            save_path = Path(file_path)
+        else:
+            save_path = file_path
+
+        directory_path = save_path.parent
+        directory_path.mkdir(parents=True, exist_ok=True)
+
+        # Fetch dictionary to save
+        prompt_dict = self.dict()
+
+        if save_path.suffix == ".json":
+            with open(file_path, "w") as f:
+                json.dump(prompt_dict, f, indent=4)
+        elif save_path.suffix == ".yaml":
+            with open(file_path, "w") as f:
+                yaml.dump(prompt_dict, f, default_flow_style=False)
+        else:
+            raise ValueError(f"{save_path} must be json or yaml")
+
+    @classmethod
+    @abstractmethod
+    def from_template(cls, template: str, **kwargs: Any) -> BasePromptTemplate:
+        """Create a prompt from a template."""
+
+    @classmethod
+    @abstractmethod
+    def from_config(cls, config: Dict) -> BasePromptTemplate:
+        """Create a prompt from config."""
diff --git a/omagent_core/models/llms/prompt/formatter.py b/omagent_core/models/llms/prompt/formatter.py
new file mode 100644
index 0000000000000000000000000000000000000000..4da75cd43f71d4ce13a931dcc6b27138b0a2e78e
--- /dev/null
+++ b/omagent_core/models/llms/prompt/formatter.py
@@ -0,0 +1,86 @@
+from abc import ABC, abstractmethod
+from string import Formatter
+from typing import Any, List, Mapping, Sequence, Set, Union
+
+
+class BaseFormatter(ABC):
+    @abstractmethod
+    def format(self, template: str, **kwargs: Any) -> str:
+        """Format a template."""
+
+    @abstractmethod
+    def validate(self, template: str, input_variables: List[str]) -> None:
+        """Validate a template."""
+
+
+class FStringFormatter(Formatter, BaseFormatter):
+    """A subclass of formatter that checks for extra keys."""
+
+    def check_unused_args(
+        self,
+        used_args: Sequence[Union[int, str]],
+        args: Sequence,
+        kwargs: Mapping[str, Any],
+    ) -> None:
+        """Check to see if extra parameters are passed."""
+        extra = set(kwargs).difference(used_args)
+        if extra:
+            raise KeyError(extra)
+
+    def vformat(
+        self, format_string: str, args: Sequence, kwargs: Mapping[str, Any]
+    ) -> str:
+        """Check that no arguments are provided."""
+        if len(args) > 0:
+            raise ValueError(
+                "No arguments should be provided, "
+                "everything should be passed as keyword arguments."
+            )
+        return super().vformat(format_string, args, kwargs)
+
+    def validate(self, template: str, input_variables: List[str]) -> None:
+        dummy_inputs = {input_variable: "foo" for input_variable in input_variables}
+        super().format(template, **dummy_inputs)
+
+
+class JinjiaFormatter(BaseFormatter):
+    def format(self, template: str, **kwargs: Any) -> str:
+        """Format a template using jinja2."""
+        try:
+            from jinja2 import Template
+        except ImportError:
+            raise ImportError(
+                "jinja2 not installed, which is needed to use the jinja2_formatter. "
+                "Please install it with `pip install jinja2`."
+            )
+
+        return Template(template).render(**kwargs)
+
+    def validate(self, template: str, input_variables: List[str]) -> None:
+        input_variables_set = set(input_variables)
+        valid_variables = self._get_jinja2_variables_from_template(template)
+        missing_variables = valid_variables - input_variables_set
+        extra_variables = input_variables_set - valid_variables
+
+        error_message = ""
+        if missing_variables:
+            error_message += f"Missing variables: {missing_variables} "
+
+        if extra_variables:
+            error_message += f"Extra variables: {extra_variables}"
+
+        if error_message:
+            raise KeyError(error_message.strip())
+
+    def _get_jinja2_variables_from_template(self, template: str) -> Set[str]:
+        try:
+            from jinja2 import Environment, meta
+        except ImportError:
+            raise ImportError(
+                "jinja2 not installed, which is needed to use the jinja2_formatter. "
+                "Please install it with `pip install jinja2`."
+            )
+        env = Environment()
+        ast = env.parse(template)
+        variables = meta.find_undeclared_variables(ast)
+        return variables
diff --git a/omagent_core/models/llms/prompt/parser.py b/omagent_core/models/llms/prompt/parser.py
new file mode 100644
index 0000000000000000000000000000000000000000..07cd5f4089fa9c3169d7d9750260ec414849d756
--- /dev/null
+++ b/omagent_core/models/llms/prompt/parser.py
@@ -0,0 +1,95 @@
+import json
+import re
+from abc import ABC, abstractmethod
+from typing import Generic, Optional, TypeVar
+
+from ....utils.error import VQLError
+from ..base import BotBase
+
+T = TypeVar("T")
+
+
+class BaseOutputParser(BotBase, ABC, Generic[T]):
+    """Class to parse the output of an LLM call.
+
+    Output parsers help structure language model responses.
+    """
+
+    regex: Optional[str] = None
+    regex_group: Optional[int] = 0
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "forbid"
+
+    @abstractmethod
+    def _parse(self, text: str) -> T:
+        """Parse the output of an LLM call.
+
+        A method which takes in a string (assumed output of language model )
+        and parses it into some structure.
+
+        Args:
+            text: output of language model
+
+        Returns:
+            structured output
+        """
+
+    def parse(self, text: str) -> T:
+        if self.regex:
+            regex_res = re.search(self.regex, text)
+            if regex_res is not None:
+                text = regex_res.group(self.regex_group)
+            else:
+                raise VQLError(
+                    800, detail=f"Not valid json [{text}] for regex [{self.regex}]"
+                )
+        return self._parse(text)
+
+    @property
+    def _type(self) -> str:
+        """Return the type key."""
+        raise self.type
+
+
+class DictParser(BaseOutputParser):
+    def _fix_json_input(self, input_str: str) -> str:
+        # Replace single backslashes with double backslashes,
+        # while leaving already escaped ones intact
+        corrected_str = re.sub(
+            r'(?<!\\)\\(?!["\\/bfnrt]|u[0-9a-fA-F]{4})', r"\\\\", input_str
+        )
+        corrected_str.replace("'", '"')
+        return corrected_str
+
+    def _find_json(self, input_str: str) -> dict:
+        match = input_str.find("{")
+        result, index = json.JSONDecoder().raw_decode(input_str[match:])
+        return result
+
+    def _parse(self, text: str) -> dict:
+        try:
+            parsed = self._find_json(text)
+        except json.JSONDecodeError:
+            preprocessed_text = self._fix_json_input(text)
+            try:
+                parsed = self._find_json(preprocessed_text)
+            except Exception:
+                raise VQLError(800, detail=f"Not valid json [{text}]")
+        return parsed
+
+
+class ListParser(BaseOutputParser):
+    separator: str = ","
+
+    def _parse(self, text: str) -> list:
+        res_list = text.split(self.separator)
+        res_list = [x.strip() for x in res_list]
+        return res_list
+
+
+class StrParser(BaseOutputParser):
+    def _parse(self, text: str) -> str:
+        return text
diff --git a/omagent_core/models/llms/prompt/prompt.py b/omagent_core/models/llms/prompt/prompt.py
new file mode 100644
index 0000000000000000000000000000000000000000..2bac525ad08d94ced29c2266c8133b050000f9af
--- /dev/null
+++ b/omagent_core/models/llms/prompt/prompt.py
@@ -0,0 +1,170 @@
+from __future__ import annotations
+
+from pathlib import Path
+import os
+from string import Formatter
+from typing import Any, Dict, List, Union
+
+from pydantic import model_validator
+
+from ....utils.registry import registry
+from .base import (DEFAULT_FORMATTER_MAPPING, BasePromptTemplate,
+                   _get_jinja2_variables_from_template, check_valid_template)
+
+
+@registry.register_prompt()
+class PromptTemplate(BasePromptTemplate):
+    """Schema to represent a prompt for an LLM.
+
+    Example:
+        .. code-block:: python
+
+            prompt = PromptTemplate(input_variables=["foo"], template="Say {foo}")
+    """
+
+    # input_variables: List[str]
+    template: str
+    """The prompt template."""
+
+    template_format: str = "jinja2"
+    """The format of the prompt template. Options are: 'f-string', 'jinja2'."""
+
+    validate_template: bool = True
+    """Whether or not to try validating the template."""
+
+    role: str = "user"
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+
+    def __init__(self, **kwargs: Any):
+        super().__init__(**kwargs)
+        input_variables = kwargs.get("input_variables", [])
+        pre_filled_kv = {key: kwargs[key] for key in input_variables if key in kwargs.keys()}
+        if pre_filled_kv:
+            self.template = self.format(**pre_filled_kv)
+            input_variables = list(set(input_variables) - set(pre_filled_kv.keys()))
+            self.input_variables = input_variables
+
+    def format(self, **kwargs: Any) -> str:
+        """Format the prompt with the inputs.
+
+        Args:
+            kwargs: Any arguments to be passed to the prompt template.
+
+        Returns:
+            A formatted string.
+
+        Example:
+
+        .. code-block:: python
+
+            prompt.format(variable1="foo")
+        """
+        kwargs = self._merge_partial_and_user_variables(**kwargs)
+        return DEFAULT_FORMATTER_MAPPING[self.template_format].format(
+            self.template, **kwargs
+        )
+
+    @model_validator(mode="after")
+    def template_is_valid(self) -> "PromptTemplate":
+        """Check that template and input variables are consistent."""
+        if self.validate_template:
+            all_inputs = self.input_variables + list(self.partial_variables)
+            check_valid_template(self.template, self.template_format, all_inputs)
+        return self
+
+    @classmethod
+    def from_examples(
+        cls,
+        examples: List[str],
+        suffix: str,
+        input_variables: List[str],
+        example_separator: str = "\n\n",
+        prefix: str = "",
+        **kwargs: Any,
+    ) -> PromptTemplate:
+        """Take examples in list format with prefix and suffix to create a prompt.
+
+        Intended to be used as a way to dynamically create a prompt from examples.
+
+        Args:
+            examples: List of examples to use in the prompt.
+            suffix: String to go after the list of examples. Should generally
+                set up the user's input.
+            input_variables: A list of variable names the final prompt template
+                will expect.
+            example_separator: The separator to use in between examples. Defaults
+                to two new line characters.
+            prefix: String that should go before any examples. Generally includes
+                examples. Default to an empty string.
+
+        Returns:
+            The final prompt generated.
+        """
+        template = example_separator.join([prefix, *examples, suffix])
+        return cls(input_variables=input_variables, template=template, **kwargs)
+
+    @classmethod
+    def find_file(cls, start_dir, file_name):
+        for root, dirs, files in os.walk(start_dir):
+            if file_name in files:
+                return os.path.join(root, file_name)
+        return None
+
+    @classmethod
+    def from_file(
+        cls, template_file: Union[str, Path], **kwargs: Any
+    ) -> PromptTemplate:
+        """Load a prompt from a file.
+
+        Args:
+            template_file: The path to the file containing the prompt template.
+            input_variables: A list of variable names the final prompt template
+                will expect.
+        Returns:
+            The prompt loaded from the file.
+        """
+        original_file = template_file
+        while True:
+            if os.path.exists(template_file):
+                with open(template_file, "r") as f:
+                    template = f.read()
+                return cls.from_template(template=template, **kwargs)
+            
+            if "/" in template_file:
+                template_file = "/".join(template_file.split("/", 1)[1:])
+            else:
+                raise ValueError(f"the prompt file path ({original_file}) is not valid")
+
+    @classmethod
+    def from_template(
+        cls, template: str, template_format: str = "jinja2", **kwargs: Any
+    ) -> PromptTemplate:
+        """Load a prompt template from a template."""
+        if template_format == "jinja2":
+            # Get the variables for the template
+            input_variables = _get_jinja2_variables_from_template(template)
+
+        else:
+            input_variables = {
+                v for _, v, _, _ in Formatter().parse(template) if v is not None
+            }
+
+        return cls(
+            input_variables=list(sorted(input_variables)),
+            template=template,
+            template_format=template_format,
+            **kwargs,
+        )
+
+    @classmethod
+    def from_config(cls, config: Dict) -> PromptTemplate:
+        """Load a prompt template from a config."""
+        template = config.pop("template")
+        if template.endswith(".prompt"):
+            return cls.from_file(template, **config)
+        else:
+            return cls.from_template(template, **config)
diff --git a/omagent_core/models/llms/qwen2.py b/omagent_core/models/llms/qwen2.py
new file mode 100644
index 0000000000000000000000000000000000000000..19c88c99976d6d31a73aa79d3754ab027ac654f8
--- /dev/null
+++ b/omagent_core/models/llms/qwen2.py
@@ -0,0 +1,108 @@
+import os
+from datetime import datetime
+from typing import Any, Dict, List
+
+from pydantic import Field
+from .schemas import Content, Message
+from ...utils.registry import registry
+from .base import BaseLLM
+import torch
+import sysconfig
+import geocoder
+
+
+BASIC_SYS_PROMPT = """You are an intelligent agent that can help in many regions. 
+Following are some basic information about your working environment, please try your best to answer the questions based on them if needed. 
+Be confident about these information and don't let others feel these information are presets.
+Be concise.
+---BASIC INFORMATION---
+Current Datetime: {}
+Operating System: {}"""
+
+
+@registry.register_llm()
+class Qwen2LLM(BaseLLM):
+    model_name: str = Field(default=os.getenv("MODEL_NAME", "Qwen/Qwen2.5-1.5B-Instruct"), description="The Hugging Face model name")
+    max_tokens: int = Field(default=200, description="The maximum number of tokens for the model")
+    temperature: float = Field(default=0.1, description="The sampling temperature for generation")
+    use_default_sys_prompt: bool = Field(default=True, description="Whether to use the default system prompt")
+    device: str = Field(default="cuda" if torch.cuda.is_available() else "cpu", description="The device to run the model on")
+    vision: bool = Field(default=False, description="Whether the model supports vision")
+
+    def __init__(self, **data: Any) -> None:
+        super().__init__(**data)
+        from transformers import AutoModelForCausalLM, AutoTokenizer, GenerationConfig, pipeline
+        self.tokenizer = AutoTokenizer.from_pretrained(self.model_name)
+        self.model = AutoModelForCausalLM.from_pretrained(self.model_name).to(self.device)        
+
+    def _call(self, records: List[Message], **kwargs) -> Dict:        
+        prompts = self._generate_prompt(records)
+        text = self.tokenizer.apply_chat_template(
+            prompts,
+            tokenize=False,
+            add_generation_prompt=True
+        )   
+        model_inputs = self.tokenizer([text], return_tensors="pt").to(self.model.device)
+        generated_ids = self.model.generate(
+            **model_inputs,
+            max_new_tokens=self.max_tokens
+        )
+        generated_ids = [
+            output_ids[len(input_ids):] for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
+        ]
+        response = self.tokenizer.batch_decode(generated_ids, skip_special_tokens=True)
+        return {"responses": response}
+
+
+    async def _acall(self, records: List[Message], **kwargs) -> Dict:
+        raise NotImplementedError("Async calls are not yet supported for Hugging Face models.")
+
+    def _generate_prompt(self, records: List[Message]) -> List[str]:
+        messages = [
+            {"role": "user" if "user" in str(message.role) else "system", "content": self._get_content(message.content)}
+            for message in records
+        ]        
+        if self.use_default_sys_prompt:
+            messages = [self._generate_default_sys_prompt()] + messages
+        print ("messages:",messages)
+        return messages
+
+
+    def _generate_default_sys_prompt(self) -> Dict:
+        loc = self._get_location()
+        os = self._get_linux_distribution()
+        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        promt_str = BASIC_SYS_PROMPT.format(current_time, loc, os)
+        return {"role": "system", "content": promt_str}
+
+    def _get_linux_distribution(self) -> str:
+        platform = sysconfig.get_platform()
+        if "linux" in platform:
+            if os.path.exists("/etc/lsb-release"):
+                with open("/etc/lsb-release", "r") as f:
+                    for line in f:
+                        if line.startswith("DISTRIB_DESCRIPTION="):
+                            return line.split("=")[1].strip()
+            elif os.path.exists("/etc/os-release"):
+                with open("/etc/os-release", "r") as f:
+                    for line in f:
+                        if line.startswith("PRETTY_NAME="):
+                            return line.split("=")[1].strip()
+        return platform
+
+    def _get_location(self) -> str:
+        g = geocoder.ip("me")
+        if g.ok:
+            return g.city + "," + g.country
+        else:
+            return "unknown"
+
+
+    @staticmethod
+    def _get_content(content: Content | List[Content]) -> str:
+        if isinstance(content, list):
+            return " ".join(c.text for c in content if c.type == "text")
+        elif isinstance(content, Content) and content.type == "text":
+            return content.text
+        else:
+            raise ValueError("Invalid content type")
\ No newline at end of file
diff --git a/omagent_core/models/llms/qwen2_vl.py b/omagent_core/models/llms/qwen2_vl.py
new file mode 100644
index 0000000000000000000000000000000000000000..c838358f70cc91ba790ae2af3a7f75707a7899ea
--- /dev/null
+++ b/omagent_core/models/llms/qwen2_vl.py
@@ -0,0 +1,124 @@
+import os
+from datetime import datetime
+from typing import Any, Dict, List
+
+from pydantic import Field
+from .schemas import Content, Message
+from ...utils.registry import registry
+from .base import BaseLLM
+import torch
+import sysconfig
+import geocoder
+from qwen_vl_utils import process_vision_info
+
+BASIC_SYS_PROMPT = """You are an intelligent agent that can help in many regions. 
+Following are some basic information about your working environment, please try your best to answer the questions based on them if needed. 
+Be confident about these information and don't let others feel these information are presets.
+Be concise.
+---BASIC INFORMATION---
+Current Datetime: {}
+Operating System: {}"""
+
+
+@registry.register_llm()
+class Qwen2_VL(BaseLLM):
+    model_name: str = Field(default=os.getenv("MODEL_NAME", "Qwen/Qwen2-VL-2B-Instruct"), description="The Hugging Face model name")
+    max_tokens: int = Field(default=128, description="The maximum number of tokens for the model")
+    temperature: float = Field(default=0.1, description="The sampling temperature for generation")
+    use_default_sys_prompt: bool = Field(default=False, description="Whether to use the default system prompt")
+    device: str = Field(default="cuda" if torch.cuda.is_available() else "cpu", description="The device to run the model on")
+
+    def __init__(self, **data: Any) -> None:
+        super().__init__(**data)
+        from transformers import AutoTokenizer, AutoProcessor, Qwen2VLForConditionalGeneration
+        self.processor = AutoProcessor.from_pretrained(self.model_name,min_pixels=256 * 28 * 28, max_pixels=512 * 28 * 28)
+        self.model = Qwen2VLForConditionalGeneration.from_pretrained(self.model_name, torch_dtype="auto", device_map="auto").to(self.device)
+        
+
+    def _call(self, records: List[Message], **kwargs) -> Dict:
+        prompts, image_inputs, video_inputs = self._prepare_inputs(records)
+        inputs = self.processor(
+            text=[prompts],
+            images=image_inputs,
+            videos=video_inputs,
+            padding=True,
+            return_tensors="pt",
+        ).to(self.device)
+        
+        inputs.input_ids = inputs.input_ids.to(self.device)
+
+        generated_ids = self.model.generate(
+            **inputs,
+            max_new_tokens=self.max_tokens
+        )
+        generated_ids_trimmed = [
+            out_ids[len(in_ids):] for in_ids, out_ids in zip(inputs.input_ids.to(self.device), generated_ids)
+        ]
+        response = self.processor.batch_decode(
+            generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
+        )
+        return {"responses": response}
+
+    async def _acall(self, records: List[Message], **kwargs) -> Dict:
+        raise NotImplementedError("Async calls are not yet supported for Hugging Face models.")
+    
+    def convert_messages(self, messages):
+        for message in messages:
+            for content in message.get("content", []):
+                if content.get("type") == "image" and "data" in content:
+                    content["image"] = content.pop("data")
+                if content.get("type") == "text" and "data" in content:
+                    content["text"] = content.pop("data")
+        return messages
+
+    def _prepare_inputs(self, records: List[Message]):
+        records = records["messages"]
+        records = self.convert_messages(records) 
+        prompts = self._generate_prompt(records)
+        image_inputs, video_inputs = process_vision_info(records)
+        return prompts, image_inputs, video_inputs
+
+    def _generate_prompt(self, records: List[Message]) -> str:                
+        messages = records
+        #if self.use_default_sys_prompt:
+        #    messages = [self._generate_default_sys_prompt()] + messages
+        return self.processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
+        
+
+    def _generate_default_sys_prompt(self) -> Dict:
+        loc = self._get_location()
+        os = self._get_linux_distribution()
+        current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+        prompt_str = BASIC_SYS_PROMPT.format(current_time, os)
+        return {"role": "system", "content": prompt_str}
+
+    def _get_linux_distribution(self) -> str:
+        platform = sysconfig.get_platform()
+        if "linux" in platform:
+            if os.path.exists("/etc/lsb-release"):
+                with open("/etc/lsb-release", "r") as f:
+                    for line in f:
+                        if line.startswith("DISTRIB_DESCRIPTION="):
+                            return line.split("=")[1].strip()
+            elif os.path.exists("/etc/os-release"):
+                with open("/etc/os-release", "r") as f:
+                    for line in f:
+                        if line.startswith("PRETTY_NAME="):
+                            return line.split("=")[1].strip()
+        return platform
+
+    def _get_location(self) -> str:
+        g = geocoder.ip("me")
+        if g.ok:
+            return g.city + "," + g.country
+        else:
+            return "unknown"
+
+    @staticmethod
+    def _get_content(content: Content | List[Content]) -> str:
+        if isinstance(content, list):
+            return " ".join(c.text for c in content if c.type == "text")
+        elif isinstance(content, Content) and content.type == "text":
+            return content.text
+        else:
+            raise ValueError("Invalid content type")
diff --git a/omagent_core/models/llms/schemas.py b/omagent_core/models/llms/schemas.py
new file mode 100644
index 0000000000000000000000000000000000000000..1871b4e164e80bc68eba9af1f9ee014ddd1b0072
--- /dev/null
+++ b/omagent_core/models/llms/schemas.py
@@ -0,0 +1,166 @@
+import datetime
+import re
+import time
+from enum import Enum
+from itertools import groupby
+from typing import ClassVar, Dict, List, Optional
+
+from PIL import Image
+from pydantic import BaseModel, field_validator, model_validator
+
+from ...utils.general import encode_image
+from ..od.schemas import Target
+
+
+class Role(str, Enum):
+    USER = "user"
+    ASSISTANT = "assistant"
+    SYSTEM = "system"
+
+
+class MessageType(str, Enum):
+    IMAGE = "image"
+    TEXT = "text"
+    File = "file"
+    MIXED = "mixed"
+
+
+class ImageUrl(BaseModel):
+    url: str
+    detail: str = "auto"
+
+    @field_validator("detail")
+    @classmethod
+    def validate_detail(cls, detail: str) -> str:
+        if detail not in ["high", "low", "auto"]:
+            raise ValueError(
+                'The detail can only be one of "high", "low" and "auto". {} is not valid.'.format(
+                    detail
+                )
+            )
+        return detail
+
+
+class Content(BaseModel):
+    type: str = "text"
+    text: Optional[str] = None
+    image_url: Optional[ImageUrl] = None
+
+    @model_validator(mode="after")
+    def validate(self):
+        if self.type == "text":
+            if self.text is None:
+                raise ValueError(
+                    "The text value must be valid when Content type is 'text'."
+                )
+        elif self.type == "image_url":
+            if self.image_url is None:
+                raise ValueError(
+                    "The image_url value must be valid when Content type is 'image_url'."
+                )
+        else:
+            raise ValueError(
+                "Invalid Conyent type {}. Must be one of text and image_url".format(
+                    self.type
+                )
+            )
+        return self
+
+
+class Message(BaseModel):
+    """
+    - role (str): The role of this message. Choose from 'user', 'assistant', 'system'.
+    - message_type (str): Type of the message. Choose from 'text', 'image' and 'mixed'.
+    - src_type (str): Type of the message content. If message is text, src_type='text'. If message is image, Choose from 'url', 'base64', 'local' and 'redis'.
+    - content (str): Message content.
+    - objects (List[schemas.Target]): The detected objects.
+    """
+
+    role: Role = Role.USER
+    message_type: MessageType = MessageType.TEXT
+    content: List[Content | Dict] | Content | str
+    objects: List[Target] = []
+    kwargs: dict = {}
+    basic_data_types: ClassVar[List[type]] = [
+        str,
+        list,
+        tuple,
+        int,
+        float,
+        bool,
+        datetime.datetime,
+        datetime.time,
+    ]
+
+    @classmethod
+    def merge_consecutive_text(cls, content) -> List:
+        result = []
+        current_str = ""
+
+        for part in content:
+            if isinstance(part, str):
+                current_str += part
+            else:
+                if current_str:
+                    result.append(current_str)
+                    current_str = ""
+                result.append(part)
+
+        if current_str:  # 处理最后的字符串
+            result.append(current_str)
+
+        return result
+
+    @field_validator("content", mode="before")
+    @classmethod
+    def content_validator(
+        cls, content: List[Content | Dict] | Content | str
+    ) -> List[Content] | Content:
+        if isinstance(content, str):
+            return Content(type="text", text=content)
+        elif isinstance(content, list):
+            # combine str elements in list
+            content = cls.merge_consecutive_text(content)
+            formatted = []
+            for c in content:
+                if not c:
+                    continue
+                if isinstance(c, Content):
+                    formatted.append(c)
+                elif isinstance(c, dict):
+                    try:
+                        formatted.append(Content(**c))
+                    except Exception as e:
+                        formatted.append(Content(type="text", text=str(c)))
+                elif isinstance(c, Image.Image):
+                    formatted.append(
+                        Content(
+                            type="image_url",
+                            image_url={
+                                "url": f"data:image/jpeg;base64,{encode_image(c)}"
+                            },
+                        )
+                    )
+                elif isinstance(c, tuple(cls.basic_data_types)):
+                    formatted.append(Content(type="text", text=str(c)))
+                else:
+                    raise ValueError(
+                        f"Content list must contain [Content, str, list, dict, PIL.Image], got {type(c)}"
+                    )
+        else:
+            raise ValueError(
+                "Content must be a string, a list of Content objects or list of dicts."
+            )
+        return formatted[0] if len(formatted) == 1 else formatted
+
+    @classmethod
+    def system(cls, content: str | List[str | Dict | Content]) -> "Message":
+        return cls(role=Role.SYSTEM, content=content)
+
+    @classmethod
+    def user(cls, content: str | List[str | Dict | Content]) -> "Message":
+        return cls(role=Role.USER, content=content)
+
+    @classmethod
+    def assistant(cls, content: str | List[str | Dict | Content]) -> "Message":
+        return cls(role=Role.ASSISTANT, content=content)
diff --git a/omagent_core/models/od/schemas.py b/omagent_core/models/od/schemas.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5ec0b5ed26becbd62f60b2080cc3f76c048dcce
--- /dev/null
+++ b/omagent_core/models/od/schemas.py
@@ -0,0 +1,19 @@
+from typing import List, Optional
+
+from pydantic import BaseModel
+
+
+class Target(BaseModel):
+    """
+    - bbox (List[float]): Bounding box of an object.
+    - polygon (List[List[float]]): Polygon of an object.
+    - label (str): Object label.
+    - conf (float): Object confidence.
+    - attr (List[str]): Object attributes.
+    """
+
+    bbox: Optional[List[float]] = None
+    polygon: Optional[List[List[float]]] = None
+    label: Optional[str] = None
+    conf: Optional[float] = None
+    attr: Optional[List[str]] = None
diff --git a/omagent_core/services/__init__.py b/omagent_core/services/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/services/connectors/__init__.py b/omagent_core/services/connectors/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/services/connectors/base.py b/omagent_core/services/connectors/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..236dbca6ded27bab37118613f5ce6cd8d54457d2
--- /dev/null
+++ b/omagent_core/services/connectors/base.py
@@ -0,0 +1,9 @@
+from typing import Any
+
+from omagent_core.base import BotBase
+
+
+class ConnectorBase(BotBase):
+    @property
+    def client(self) -> Any:
+        return self._client
diff --git a/omagent_core/services/connectors/conductor.py b/omagent_core/services/connectors/conductor.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/services/connectors/milvus.py b/omagent_core/services/connectors/milvus.py
new file mode 100644
index 0000000000000000000000000000000000000000..21237f3bb891421a90dc34d39fea8d0e771a2e76
--- /dev/null
+++ b/omagent_core/services/connectors/milvus.py
@@ -0,0 +1,35 @@
+from typing import Any, Optional
+
+from omagent_core.utils.registry import registry
+from pydantic import Field
+from pymilvus import MilvusClient
+
+from .base import ConnectorBase
+
+
+@registry.register_connector()
+class MilvusConnector(ConnectorBase):
+    host: str = Field(default="./db.db")
+    port: int = Field(default=19530)
+    password: str = Field(default="")
+    username: Optional[str] = Field(default="default")
+    db: Optional[str] = Field(default="default")
+    alias: Optional[str] = Field(default="alias")
+
+    def model_post_init(self, __context: Any) -> None:
+        try:
+            self._client = MilvusClient(
+                uri=self.host,
+                user=self.username,
+                password=self.password,
+                db_name=self.db,
+            )
+        except Exception as e:
+            raise ConnectionError(
+                f"Connection to Milvus failed. Please check your connector config in container.yaml. \n Error Message: {e}"
+            )
+
+    def check_connection(self) -> bool:
+        """Check if the connection to Milvus is valid."""
+        # Try to list collections to verify connection
+        self._client.list_collections()
diff --git a/omagent_core/services/connectors/redis.py b/omagent_core/services/connectors/redis.py
new file mode 100644
index 0000000000000000000000000000000000000000..95f69d5a755dc764579bb348cbbfff695480a6f5
--- /dev/null
+++ b/omagent_core/services/connectors/redis.py
@@ -0,0 +1,82 @@
+from typing import Any, Optional
+import threading
+import os
+import atexit
+
+from omagent_core.utils.registry import registry
+from pydantic import Field, PrivateAttr
+from redis import ConnectionPool, Redis
+from redislite import Redis as RedisLite
+
+from .base import ConnectorBase
+
+
+class SharedRedisLite:
+    """Shared Redis implementation using RedisLite"""
+    _instance = None
+    _lock = threading.Lock()
+    _DB_FILE = os.path.join(os.getcwd(), "shared_redis.db")
+
+    @classmethod
+    def get_instance(cls):
+        with cls._lock:
+            if cls._instance is None:
+                # Create shared RedisLite instance using fixed database file
+                cls._instance = RedisLite(
+                    dbfilename=cls._DB_FILE,
+                    serverconfig={
+                        'save': '',  # Disable persistence
+                        'appendonly': 'no'  # Disable AOF
+                    }
+                )
+                # Register cleanup function
+                atexit.register(cls._cleanup)
+        return cls._instance
+
+    @classmethod
+    def _cleanup(cls):
+        """Clean up database files when the program exits"""
+        if cls._instance is not None:
+            cls._instance.shutdown()
+            # Remove database file and settings file
+            for suffix in ['', '.settings']:
+                file_path = cls._DB_FILE + suffix
+                if os.path.exists(file_path):
+                    try:
+                        os.remove(file_path)
+                    except OSError:
+                        pass
+
+
+@registry.register_connector()
+class RedisConnector(ConnectorBase):
+    host: str = Field(default="localhost")
+    port: int = Field(default=6379)
+    password: Optional[str] = Field(default=None)
+    username: Optional[str] = Field(default=None)
+    db: int = Field(default=0)
+    use_lite: bool = Field(default=True)
+    
+    _client: Any = PrivateAttr(default=None)
+
+    def model_post_init(self, __context: Any) -> None:
+        if self.use_lite:
+            self._client = SharedRedisLite.get_instance()
+        else:
+            pool = ConnectionPool(
+                host=self.host,
+                port=self.port,
+                password=self.password,
+                username=self.username,
+                db=self.db,
+                decode_responses=False,
+            )
+            self._client = Redis(connection_pool=pool)
+
+    def check_connection(self) -> bool:
+        """Check if Redis connection is valid by executing a simple ping command"""
+        try:
+            self._client.ping()
+            return True
+        except Exception as e:
+            raise ConnectionError(f"Redis connection failed: {str(e)}")
diff --git a/omagent_core/services/handlers/__init__.py b/omagent_core/services/handlers/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/services/handlers/milvus_handler.py b/omagent_core/services/handlers/milvus_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..647eb16a6f0bb217fc93ecf6e580548aaf3ce390
--- /dev/null
+++ b/omagent_core/services/handlers/milvus_handler.py
@@ -0,0 +1,271 @@
+from typing import Any
+from uuid import uuid4
+
+import numpy as np
+from omagent_core.utils.error import VQLError
+from omagent_core.utils.registry import registry
+from pydantic import BaseModel
+from pymilvus import Collection, DataType, MilvusClient, connections, utility
+from pymilvus.client import types
+
+
+@registry.register_component()
+class MilvusHandler(BaseModel):
+    host_url: str = "./memory.db"
+    user: str = ""
+    password: str = ""
+    db_name: str = "default"
+    primary_field: Any = None
+    vector_field: Any = None
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    def __init__(self, **data: Any):
+        super().__init__(**data)
+        self.milvus_client = MilvusClient(
+            uri=self.host_url,
+            user=self.user,
+            password=self.password,
+            db_name=self.db_name,
+        )
+
+    def is_collection_in(self, collection_name):
+        """
+        Check if a collection exists in Milvus.
+
+        Args:
+            collection_name (str): The name of the collection to check.
+
+        Returns:
+            bool: True if the collection exists, False otherwise.
+        """
+        return self.milvus_client.has_collection(collection_name)
+
+    def make_collection(self, collection_name, schema):
+        """
+        Create a new collection in Milvus.
+
+        This method will first check if a collection with the given name already exists.
+        If it does, it will print a message and do nothing.
+        If it doesn't, it will create a new collection with the given name and schema,
+        and then create an index for the vector field in the collection.
+
+        Args:
+            collection_name (str): The name of the collection to create.
+            schema (CollectionSchema): The schema of the collection to create.
+
+        Raises:
+            VQLError: If the schema does not have exactly one primary key.
+        """
+
+        index_params = self.milvus_client.prepare_index_params()
+        for field in schema.fields:
+            if (
+                field.dtype == DataType.FLOAT_VECTOR
+                or field.dtype == DataType.BINARY_VECTOR
+            ):
+                index_params.add_index(
+                    field_name=field.name,
+                    index_name=field.name,
+                    index_type="FLAT",
+                    metric_type="COSINE",
+                    params={"nlist": 128},
+                )
+                print(f"{field.name} of {collection_name} index created")
+
+        if self.is_collection_in(collection_name):
+            print(f"{collection_name} collection already exists")
+        else:
+            self.milvus_client.create_collection(
+                collection_name, schema=schema, index_params=index_params
+            )
+            print(f"Create collection {collection_name} successfully")
+
+    def drop_collection(self, collection_name):
+        """
+        Drop a collection in Milvus.
+
+        This method will first check if a collection with the given name exists.
+        If it does, it will drop the collection and print a success message.
+        If it doesn't, it will print a message indicating that the collection does not exist.
+
+        Args:
+            collection_name (str): The name of the collection to drop.
+        """
+        if self.is_collection_in(collection_name):
+            self.milvus_client.drop_collection(collection_name)
+            print(f"Drop collection {collection_name} successfully")
+        else:
+            print(f"{collection_name} collection does not exist")
+
+    def do_add(self, collection_name, vectors):
+        """
+        Add vectors to a collection in Milvus.
+
+        This method will first check if a collection with the given name exists.
+        If it does, it will add the vectors to the collection and return the IDs of the added vectors.
+        If it doesn't, it will raise a VQLError.
+
+        Args:
+            collection_name (str): The name of the collection to add vectors to.
+            vectors (list): The vectors to add to the collection.
+
+        Returns:
+            list: The IDs of the added vectors.
+
+        Raises:
+            VQLError: If the collection does not exist.
+        """
+        if self.is_collection_in(collection_name):
+            res = self.milvus_client.insert(
+                collection_name=collection_name, data=vectors
+            )
+            return res["ids"]
+        else:
+            raise VQLError(500, detail=f"{collection_name} collection does not exist")
+
+    def match(
+        self,
+        collection_name,
+        query_vectors: list,
+        query_field,
+        output_fields: list = None,
+        res_size=10,
+        filter_expr="",
+        threshold=0,
+    ):
+        """
+        Perform a vector similarity search in a specified collection in Milvus.
+
+        This method will first check if a collection with the given name exists.
+        If it does, it will perform a vector similarity search using the provided query vectors,
+        and return the search results.
+        If it doesn't, it will raise a VQLError.
+
+        Args:
+            collection_name (str): The name of the collection to search in.
+            query_vectors (list): The vectors to use as query for the search.
+            query_field (str): The field to perform the search on.
+            output_fields (list): The fields to include in the search results.
+            res_size (int): The maximum number of search results to return.
+            filter_expr (str): The filter expression to apply during the search.
+            threshold (float): The threshold for the similarity search.
+
+        Returns:
+            list: The search results.
+
+        Raises:
+            VQLError: If the collection does not exist.
+        """
+        if self.is_collection_in(collection_name):
+            search_params = {
+                "metric_type": "COSINE",
+                "ignore_growing": False,
+                "params": {
+                    "nprobe": 10,
+                    "radius": 2 * threshold - 1,
+                    "range_filter": 1,
+                },
+            }
+            hits = self.milvus_client.search(
+                collection_name=collection_name,
+                data=query_vectors,
+                anns_field=query_field,
+                search_params=search_params,
+                limit=res_size,
+                output_fields=output_fields,
+                filter=filter_expr,
+            )
+
+            return hits
+        else:
+            raise VQLError(500, detail=f"{collection_name} collection does not exist")
+
+    def delete_doc_by_ids(self, collection_name, ids):
+        """
+        Delete specific documents in a collection in Milvus by their IDs.
+
+        This method will first check if a collection with the given name exists.
+        If it does, it will delete the documents with the provided IDs from the collection.
+        If it doesn't, it will raise a VQLError.
+
+        Args:
+            collection_name (str): The name of the collection to delete documents from.
+            ids (list): The IDs of the documents to delete.
+
+        Raises:
+            VQLError: If the collection does not exist.
+        """
+        if self.is_collection_in(collection_name):
+            delete_expr = f"{self.primary_field} in {ids}"
+            res = self.milvus_client.delete(
+                collection_name=collection_name, filter=delete_expr
+            )
+            return res
+        else:
+            raise VQLError(500, detail=f"{collection_name} collection does not exist")
+
+    def delete_doc_by_expr(self, collection_name, expr):
+        """
+        Delete specific documents in a collection in Milvus by an expression.
+
+        This method will first check if a collection with the given name exists.
+        If it does, it will delete the documents that match the provided expression from the collection.
+        If it doesn't, it will raise a VQLError.
+
+        Args:
+            collection_name (str): The name of the collection to delete documents from.
+            expr (str): The expression to match the documents to delete.
+
+        Raises:
+            VQLError: If the collection does not exist.
+        """
+        if self.is_collection_in(collection_name):
+            self.milvus_client.delete(collection_name=collection_name, filter=expr)
+        else:
+            raise VQLError(500, detail=f"{collection_name} collection does not exist")
+
+
+if __name__ == "__main__":
+    from pymilvus import CollectionSchema, DataType, FieldSchema
+
+    milvus_handler = MilvusHandler()
+    rng = np.random.default_rng()
+    pk = FieldSchema(
+        name="pk",
+        dtype=DataType.VARCHAR,
+        is_primary=True,
+        auto_id=False,
+        max_length=100,
+    )
+    bot_id = FieldSchema(name="bot_id", dtype=DataType.VARCHAR, max_length=50)
+    vector = FieldSchema(name="vector", dtype=DataType.FLOAT_VECTOR, dim=512)
+    schema = CollectionSchema(
+        fields=[pk, bot_id, vector],
+        description="this is test",
+    )
+
+    data = [
+        {
+            "pk": str(uuid4()),
+            "bot_id": str(uuid4()),
+            # rng.random((1, 512))
+            "vector": [1.0, 2.0] * 256,
+        }
+    ]
+    milvus_handler.drop_collection("test1")
+    milvus_handler.make_collection("test1", schema)
+    add_detail = milvus_handler.do_add("test1", data)
+    print(add_detail)
+    print(milvus_handler.milvus_client.describe_index("test1", "vector"))
+    test_data = [[1.0, 2.0] * 256, [100, 400] * 256]
+    match_result = milvus_handler.match(
+        "test1", test_data, "vector", ["pk"], 10, "", 0.65
+    )
+    print(match_result)
+    # milvus_handler.primary_field = "pk"
+    # milvus_handler.delete_doc_by_ids("test1", ["1f764837-b80b-4788-ad8c-7a89924e343b"])
diff --git a/omagent_core/services/handlers/sql_data_handler.py b/omagent_core/services/handlers/sql_data_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..7934b0b6dbc4f448e9ba8ff632c8541dc68bdb53
--- /dev/null
+++ b/omagent_core/services/handlers/sql_data_handler.py
@@ -0,0 +1,148 @@
+from typing import Any, Dict, List, Union
+
+from omagent_core.utils.error import VQLError
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+from pydantic import BaseModel
+from sqlalchemy import text
+from sqlalchemy_utils import create_database, database_exists
+from sqlmodel import Session, SQLModel, create_engine, delete, select
+
+from .table import BaseTable
+
+
+class SQLDataHandler(BaseModel):
+    """
+    数据库操作基础类
+    """
+
+    # 获取数据库相关环境变量
+    db: str
+    user: str
+    passwd: str
+    host: str = "localhost"
+    port: str = 3306
+
+    table: Any
+
+    DELETED: int = 1
+    NO_DELETED: int = 0
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    def __init__(self, **data: Any) -> None:
+        """
+        从环境变量,初始化数据库,创建数据库,创建表。
+        """
+        super().__init__(**data)
+
+        self.alchemy_uri = "mysql+pymysql://%s:%s@%s:%s/%s" % (
+            self.user,
+            self.passwd,
+            self.host,
+            self.port,
+            self.db,
+        )  # 数据库连接url
+        self.engine = create_engine(
+            self.alchemy_uri,
+            pool_pre_ping=True,
+            pool_size=50,
+            max_overflow=4,
+            pool_recycle=7200,
+            pool_timeout=600,
+            echo=True,
+            pool_use_lifo=True,
+        )
+
+        if not database_exists(self.engine.url):
+            # Create database is not exists
+            create_database(self.engine.url)
+        else:
+            # Connect the database if exists.
+            self.engine.connect()
+
+        SQLModel.metadata.create_all(bind=self.engine)
+
+    def execute_sql(self, sql_query: str):
+        matches = []
+        with self.engine.connect() as connection:
+            result = connection.execute(text(sql_query))
+            for row in result:
+                matches.append(row._asdict())
+        return matches
+
+    def simple_get(self, bot_id: str, number: int = None) -> List[BaseTable]:
+        with Session(self.engine) as session:
+            statement = (
+                select(self.table)
+                .where(self.table.bot_id == bot_id)
+                .where(self.table.deleted == self.NO_DELETED)
+                .order_by(self.table.id.desc())
+            )
+            if number:
+                statement = statement.limit(number)
+            query_results = session.exec(statement).all()
+
+        if not query_results:
+            return []
+        return query_results
+
+    def simple_add(self, data: List[Union[BaseTable, Dict]]) -> List[str]:
+        """Add data to the table and return ids.
+
+        Args:
+            data (List[Union[BaseTable, Dict]]): A list of data you want to add to the table. Could be table objects or valid dicts.
+
+        Returns:
+            List[str]: The ids of the inserted rows.
+        """
+        num = 0
+        inserted = []
+        with Session(self.engine) as session:
+            for item in data:
+                if isinstance(item, dict):
+                    item = self.table(**item)
+                session.add(item)
+                num += 1
+                inserted.append(item)
+            session.commit()
+            logging.debug(f"{num} data is added to table [{self.table.__tablename__}]")
+            ids = [item.id for item in inserted]
+        return ids
+
+    def simple_update(self, id: int, key: str, value: Any):
+        with Session(self.engine) as session:
+            statement = select(self.table).where(self.table.id == id)
+            query_result = session.exec(statement).one()
+            if not query_result:
+                raise VQLError(
+                    500,
+                    detail=f"Trying to update non-existent data [{id}] in table [{self.table.__tablename__}]",
+                )
+            elif query_result.deleted == self.DELETED:
+                raise VQLError(
+                    500,
+                    detail=f"Trying to update deleted data [{id}] in table [{self.table.__tablename__}]",
+                )
+
+            query_result.__setattr__(key, value)
+            session.add(query_result)
+            session.commit()
+            logging.debug(
+                f"Key [{key}] in id [{id}] is updated to [{value}] in table [{self.table.__tablename__}]"
+            )
+
+    def simple_delete(self, id: int):
+        self.simple_update(id=id, key="deleted", value=1)
+        logging.debug(f"Id [{id}] is deleted in table [{self.table.__tablename__}]")
+
+    def init_table(self):
+        with Session(self.engine) as session:
+            result = session.exec(delete(self.table))
+            session.commit()
+            del_row_num = result.rowcount
+        return del_row_num
diff --git a/omagent_core/services/handlers/table.py b/omagent_core/services/handlers/table.py
new file mode 100644
index 0000000000000000000000000000000000000000..568dc47540955db68eae3730fd5d628b6801c63c
--- /dev/null
+++ b/omagent_core/services/handlers/table.py
@@ -0,0 +1,22 @@
+from abc import ABC
+from datetime import datetime
+from typing import Any, Dict, Optional
+
+from sqlmodel import Column, DateTime, Field, SQLModel, func
+
+
+class BaseTable(SQLModel):
+    id: Optional[int] = Field(default=None, primary_key=True)
+    bot_id: str = Field(index=True, nullable=False)
+
+    create_time: Optional[datetime] = Field(
+        sa_column=Column(DateTime(timezone=True), server_default=func.now())
+    )
+
+    update_time: Optional[datetime] = Field(
+        sa_column=Column(
+            DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
+        )
+    )
+
+    deleted: int = 0
diff --git a/omagent_core/services/handlers/video_handler.py b/omagent_core/services/handlers/video_handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..55b3b5920b11cd31a581ee609eea2d6e7446c82c
--- /dev/null
+++ b/omagent_core/services/handlers/video_handler.py
@@ -0,0 +1,127 @@
+from typing import Any, Optional
+
+from omagent_core.models.encoders.base import EncoderBase
+from omagent_core.utils.error import VQLError
+from omagent_core.utils.registry import registry
+from pydantic import field_validator
+from pymilvus import CollectionSchema, DataType, FieldSchema
+
+from .milvus_handler import MilvusHandler
+
+
+@registry.register_component()
+class VideoHandler(MilvusHandler):
+    collection_name: str
+    text_encoder: Optional[EncoderBase] = None
+    dim: int = None
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    @field_validator("text_encoder", mode="before")
+    @classmethod
+    def init_encoder(cls, text_encoder):
+        if isinstance(text_encoder, EncoderBase):
+            return text_encoder
+        elif isinstance(text_encoder, dict):
+            return registry.get_encoder(text_encoder.get("name"))(**text_encoder)
+        else:
+            raise ValueError("text_encoder must be EncoderBase or Dict")
+
+    def __init__(self, **data: Any) -> None:
+        super().__init__(**data)
+
+        self.dim = self.text_encoder.dim
+
+        _uid = FieldSchema(
+            name="_uid", dtype=DataType.INT64, is_primary=True, auto_id=True
+        )
+        video_md5 = FieldSchema(
+            name="video_md5", dtype=DataType.VARCHAR, max_length=100
+        )
+        content = FieldSchema(name="content", dtype=DataType.VARCHAR, max_length=65535)
+        content_vector = FieldSchema(
+            name="content_vector", dtype=DataType.FLOAT_VECTOR, dim=self.dim
+        )
+        start_time = FieldSchema(
+            name="start_time",
+            dtype=DataType.FLOAT,
+        )
+        end_time = FieldSchema(
+            name="end_time",
+            dtype=DataType.FLOAT,
+        )
+        schema = CollectionSchema(
+            fields=[_uid, video_md5, content, content_vector, start_time, end_time],
+            description="video summary vector DB",
+            enable_dynamic_field=True,
+        )
+        self.make_collection(self.collection_name, schema)
+
+    def text_add(self, video_md5, content, start_time, end_time):
+
+        if self.text_encoder is None:
+            raise VQLError(500, detail="Missing text_encoder")
+        content_vector = self.text_encoder.infer([content])[0]
+
+        # upload_data = [
+        #     [video_md5],
+        #     [content],
+        #     [content_vector],
+        #     [start_time],
+        #     [end_time],
+        # ]
+        upload_data = [
+            {
+                "video_md5": video_md5,
+                "content": content,
+                "content_vector": content_vector,
+                "start_time": start_time,
+                "end_time": end_time,
+            }
+        ]
+
+        add_detail = self.do_add(self.collection_name, upload_data)
+        # assert add_detail.succ_count == len(upload_data)
+
+    def text_match(
+        self,
+        video_md5,
+        content,
+        threshold: float,
+        start_time=None,
+        end_time=None,
+        res_size: int = 100,
+    ):
+        # search_query = {"size": res_size, "sort": [{"_score": "desc"}],
+        #                 "include": ["content", "start_time", "end_time"]}
+        filter_expr = ""
+        if video_md5 is not None:
+            filter_expr = f"video_md5=='{video_md5}'"
+        if start_time is not None and end_time is not None:
+            filter_expr += f" and (start_time>={max(0, start_time - 10)} and end_time<={end_time + 10})"
+        elif start_time is not None:
+            filter_expr += f" and start_time>={max(0, start_time - 10)}"
+        elif end_time is not None:
+            filter_expr += f" and end_time<={end_time + 10}"
+        # text retrieve stage
+        content_vector = self.text_encoder.infer([content])[0]
+        match_res = self.match(
+            collection_name=self.collection_name,
+            query_vectors=[content_vector],
+            query_field="content_vector",
+            output_fields=["content", "start_time", "end_time"],
+            res_size=res_size,
+            threshold=threshold,
+            filter_expr=filter_expr,
+        )
+
+        output = []
+        for match in match_res[0]:
+            print(match)
+            output.append(match["entity"])
+
+        return output
diff --git a/omagent_core/services/handlers/video_scenes.py b/omagent_core/services/handlers/video_scenes.py
new file mode 100644
index 0000000000000000000000000000000000000000..6fcb4a8b05149791d27ce93a85cb2dc694f555e4
--- /dev/null
+++ b/omagent_core/services/handlers/video_scenes.py
@@ -0,0 +1,189 @@
+from typing import Dict, List, Optional, Tuple, Union
+
+import cv2
+from omagent_core.utils.logger import logging
+from PIL import Image
+from pydantic import BaseModel
+from pydub import AudioSegment
+from pydub.effects import normalize
+from scenedetect import (ContentDetector, FrameTimecode, SceneManager,
+                         VideoStream, open_video)
+
+
+class Scene(BaseModel):
+    start: FrameTimecode
+    end: FrameTimecode
+    stt_res: Optional[Dict] = None
+    summary: Optional[Dict] = None
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        arbitrary_types_allowed = True
+
+    @classmethod
+    def init(cls, start: FrameTimecode, end: FrameTimecode, summary: dict = None):
+        return cls(start=start, end=end, summary=summary)
+
+    @property
+    def conversation(self):
+        # for self deployed whisper
+        if isinstance(self.stt_res, list):
+            output_conversation = "\n".join(
+                [f"{item.get('text', None)}" for item in self.stt_res]
+            )
+        else:
+            output_conversation = self.stt_res
+        return output_conversation
+
+
+class VideoScenes(BaseModel):
+    stream: VideoStream
+    audio: Union[AudioSegment, None]
+    scenes: List[Scene]
+    frame_extraction_interval: int
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    @classmethod
+    def load(
+        cls,
+        video_path: str,
+        threshold: int = 27,
+        min_scene_len: int = 1,
+        frame_extraction_interval: int = 5,
+        show_progress: bool = False,
+    ):
+        """Load a video file.
+
+        Args:
+            video_path (str): The path of the video file. Only support local file.
+            threshold (int): The scene detection threshold.
+            min_scene_len (int): Once a cut is detected, this long time must pass before a new one can
+                be added to the scene list. Count in seconds, defaults to 1.
+            show_progress (bool, optional): Whether to display the progress bar when processing the video. Defaults to False.
+        """
+        video = open_video(video_path)
+        scene_manager = SceneManager()
+        scene_manager.add_detector(
+            ContentDetector(
+                threshold=threshold, min_scene_len=video.frame_rate * min_scene_len
+            )
+        )
+        scene_manager.detect_scenes(video, show_progress=show_progress)
+        scenes = scene_manager.get_scene_list(start_in_scene=True)
+
+        try:
+            audio = AudioSegment.from_file(video_path)
+            audio = normalize(audio)
+        except Exception as e:
+            logging.warning(f"Failed to load audio from {video_path}: {e}")
+            audio = None
+        return cls(
+            stream=video,
+            scenes=[Scene.init(*scene) for scene in scenes],
+            audio=audio,
+            frame_extraction_interval=frame_extraction_interval,
+        )
+
+    def get_video_frames(
+        self, scene: Union[int, Scene, Tuple[FrameTimecode]], interval: int = None
+    ) -> Tuple[List[Image.Image], List[float]]:
+        """Get the frames of a scene.
+
+        Args:
+            scene (Union[int, Scene, Tuple[FrameTimecode]]): The scene to get frames. Can be the index of the scene, the scene object or a tuple of start and end frame timecode.
+            interval (int, optional): The interval of the frames to get. Defaults to None.
+        Raises:
+            ValueError: If the type of scene is not int, Scene or tuple.
+
+        Returns:
+            List[ndarray]: The frames of the scene.
+        """
+        if isinstance(scene, int):
+            scene = self.scenes[scene]
+            start, end = scene.start, scene.end
+        elif isinstance(scene, Scene):
+            start, end = scene.start, scene.end
+        elif isinstance(scene, tuple):
+            start, end = scene
+        else:
+            raise ValueError(
+                f"scene should be int, Scene or tuple, not {type(scene).__name__}"
+            )
+        self.stream.seek(start)
+        frames = []
+        time_stamps = []
+        if interval is None:
+            interval = self.frame_extraction_interval * self.stream.frame_rate
+        scene_len = end.get_frames() - start.get_frames()
+        if scene_len / 10 > interval:
+            interval = int(scene_len / 10) + 1
+        for index in range(scene_len):
+            if index % interval == 0:
+                f = self.stream.read()
+                if f is False:
+                    continue
+                f = cv2.cvtColor(f, cv2.COLOR_BGR2RGB)
+                frames.append(Image.fromarray(f))
+                time_stamps.append(self.stream.position.get_seconds())
+            else:
+                self.stream.read(decode=False)
+        self.stream.seek(0)
+        return frames, time_stamps
+
+    def get_audio_clip(
+        self, scene: Union[int, Scene, Tuple[FrameTimecode]]
+    ) -> AudioSegment:
+        """Get the audio clip of a scene.
+
+        Args:
+            scene (Union[int, Scene, Tuple[FrameTimecode]]): The scene to get audio clip. Can be the index of the scene, the scene object or a tuple of start and end frame timecode.
+
+        Raises:
+            ValueError: If the type of scene is not int, Scene or tuple.
+
+        Returns:
+            AudioSegment: The audio clip of the scene.
+        """
+        if self.audio is None:
+            return None
+        if isinstance(scene, int):
+            scene = self.scenes[scene]
+            start, end = scene.start, scene.end
+        elif isinstance(scene, Scene):
+            start, end = scene.start, scene.end
+        elif isinstance(scene, tuple):
+            start, end = scene
+        else:
+            raise ValueError(
+                f"scene should be int, Scene or tuple, not {type(scene).__name__}"
+            )
+
+        return self.audio[
+            int(start.get_seconds() * 1000) : int(end.get_seconds() * 1000)
+        ]
+
+    def __len__(self):
+        return len(self.scenes)
+
+    def __iter__(self):
+        self.index = 0
+        return self
+
+    def __next__(self):
+        if self.index >= len(self.scenes):
+            raise StopIteration
+        scene = self.scenes[self.index]
+        self.index += 1
+        return scene
+
+    def __getitem__(self, index):
+        return self.scenes[index]
+
+    def __setitem__(self, index, value):
+        self.scenes[index] = value
diff --git a/omagent_core/tool_system/__init__.py b/omagent_core/tool_system/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..56793a38f6936b3fea332e5f9684a7ea892955c1
--- /dev/null
+++ b/omagent_core/tool_system/__init__.py
@@ -0,0 +1,51 @@
+from pydantic import BaseModel
+
+from ..utils.registry import registry
+from .base import BaseTool
+
+
+def simple_tool(
+    name: str = None,
+    description: str = None,
+    args_schema: BaseModel = None,
+    special_params: dict = {},
+):
+    """A decorator that generates a tool based on a function.
+
+    Args:
+        name (str, optional): The name of this tool. Defaults to the function name.
+        description (str, optional): The description of what this tool is for. Defaults to the function doc.
+        args_schema (BaseModel, optional): A schema of the tool's input. If this is specified,the input will be check and transfer to this object. Defaults to None.
+        special_params (dict, optional): Some parameters that the tool may need for execution. Defaults to {}.
+    """
+
+    def wrapper(
+        func,
+        name=name,
+        description=description,
+        args_schema=args_schema,
+        special_params=special_params,
+    ):
+        if name == None:
+            name = func.__name__
+        if description == None and func.__doc__:
+            description = func.__doc__
+        elif description == None and func.__doc__ == None:
+            raise ValueError(
+                'One of arg "description" or the function doc need to be valid intro.'
+            )
+        new_tool = BaseTool(
+            name=name,
+            description=description,
+            func=func,
+            args_schema=args_schema,
+            special_params=special_params,
+        )
+        registry.mapping["tool"][name] = new_tool
+
+        def inner_wrapper(*args, **kwargs):
+            return new_tool.run(*args, **kwargs)
+
+        return inner_wrapper
+
+    return wrapper
diff --git a/omagent_core/tool_system/base.py b/omagent_core/tool_system/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..cbb82ff97e69dc9caa11a9842360b0ccc096eda0
--- /dev/null
+++ b/omagent_core/tool_system/base.py
@@ -0,0 +1,312 @@
+import json
+from abc import ABC
+from distutils.util import strtobool
+from pathlib import Path
+from typing import Any, Callable, Dict, List, Optional, Union
+
+import yaml
+from omagent_core.base import BotBase
+from omagent_core.models.od.schemas import Target
+from omagent_core.services.handlers.sql_data_handler import SQLDataHandler
+from omagent_core.utils.error import VQLError
+from omagent_core.utils.logger import logging
+from omagent_core.utils.plot import Annotator
+from PIL import Image
+from pydantic import BaseModel, model_validator
+
+
+class ArgSchema(BaseModel):
+    """ArgSchema defines the tool input schema. Only support one layer definition. Please prevent using complex structure."""
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    class ArgInfo(BaseModel):
+        description: Optional[str]
+        type: str = "str"
+        enum: Optional[List] = None
+        required: Optional[bool] = True
+
+    @model_validator(mode="before")
+    @classmethod
+    def validate_all(cls, values):
+        for key, value in values.items():
+            if type(value) is str:
+                values[key] = cls.ArgInfo(name=value)
+            elif type(value) is dict:
+                values[key] = cls.ArgInfo(**value)
+            elif type(value) is cls.ArgInfo:
+                pass
+            else:
+                raise ValueError(
+                    "The arg type must be one of string, dict or self.ArgInfo."
+                )
+        return values
+
+    @classmethod
+    def from_file(cls, schema_file: Union[str, Path]):
+        if type(schema_file) is str:
+            schema_file = Path(schema_file)
+        if schema_file.suffix == ".json":
+            with open(schema_file, "r") as f:
+                schema = json.load(f)
+        elif schema_file.suffix == ".yaml":
+            with open(schema_file, "r") as f:
+                schema = yaml.load(f, Loader=yaml.FullLoader)
+        else:
+            raise ValueError("Only support json and yaml file.")
+        return cls(**schema)
+
+    def generate_schema(self) -> Union[dict, list]:
+        required_args = []
+        parameters = {}
+        for key, value in self.model_dump(exclude_none=True).items():
+            parameters[key] = value
+            if parameters[key].pop("required"):
+                required_args.append(key)
+        return parameters, required_args
+
+    def validate_args(self, args: dict) -> dict:
+        if type(args) is not dict:
+            raise ValueError(
+                "ArgSchema validate only support dict, not {}".format(type(args))
+            )
+        new_args = {}
+        required_fields = set(
+            [k for k, v in self.model_dump().items() if v["required"]]
+        )
+        name_mapping = {
+            "str": "string",
+            "int": "integer",
+            "float": "number",
+            "bool": "boolean",
+        }
+        for name, value in args.items():
+            if name not in self.model_dump():
+                logging.warning(
+                    "The input args includes an unnecessary parameter {}. Removed from the args.".format(
+                        name
+                    )
+                )
+                continue
+            if name_mapping[type(value).__name__] == self.model_dump()[name]["type"]:
+                if (
+                    self.model_dump()[name]["enum"]
+                    and value not in self.model_dump()[name]["enum"]
+                ):
+                    raise ValueError(
+                        "The value of {} should be one of {}, but got {}".format(
+                            name, str(self.model_dump()[name]["enum"]), value
+                        )
+                    )
+                new_args[name] = value
+            elif self.model_dump()[name]["type"] == "string":
+                try:
+                    new_args[name] = str(value)
+                except:
+                    raise ValueError(
+                        "Parameter {} type expect a str value, but got a {} {}".format(
+                            name, type(value), value
+                        )
+                    )
+            elif self.model_dump()[name]["type"] == "integer":
+                try:
+                    new_args[name] = int(value)
+                except:
+                    raise ValueError(
+                        "Parameter {} type expect an int value, but got a {} {}".format(
+                            name, type(value), value
+                        )
+                    )
+            elif self.model_dump()[name]["type"] == "number":
+                try:
+                    new_args[name] = float(value)
+                except:
+                    raise ValueError(
+                        "Parameter {} type expect a float value, but got a {} {}".format(
+                            name, type(value), value
+                        )
+                    )
+            elif self.model_dump()[name]["type"] == "boolean":
+                if type(value) is bool:
+                    new_args[name] = value
+                else:
+                    try:
+                        new_args[name] = strtobool(str(value))
+                    except:
+                        raise ValueError(
+                            "Parameter {} type expect a boolean value, but got a {} {}".format(
+                                name, type(value), value
+                            )
+                        )
+            else:
+                raise ValueError(
+                    "Parameter {} type expect one of string, integer, number and boolean, but got a {} {}".format(
+                        name, self.model_dump()[name]["type"], type(value), value
+                    )
+                )
+
+        if required_fields - set(new_args.keys()):
+            raise VQLError(
+                "The required fields {} are missing.".format(
+                    required_fields - set(new_args.keys())
+                )
+            )
+        return new_args
+
+
+class BaseTool(BotBase, ABC):
+    description: str
+    func: Optional[Callable] = None
+    args_schema: Optional[ArgSchema]
+    special_params: Dict = {}
+
+    def model_post_init(self, __context: Any) -> None:
+        for _, attr_value in self.__dict__.items():
+            if isinstance(attr_value, BotBase):
+                attr_value._parent = self
+
+    @property
+    def workflow_instance_id(self) -> str:
+        if hasattr(self, "_parent"):
+            return self._parent.workflow_instance_id
+        return None
+
+    @workflow_instance_id.setter
+    def workflow_instance_id(self, value: str):
+        if hasattr(self, "_parent"):
+            self._parent.workflow_instance_id = value
+
+    def _run(self, **input) -> str:
+        """Implement this function or pass 'func' arg when initializing."""
+        return self.func(**input)
+
+    async def _arun(self, **input) -> str:
+        """Implement this function or pass 'func' arg when initializing."""
+        return await self.func(**input)
+
+    def run(self, input: Any) -> str:
+        if self.args_schema != None:
+            if type(input) != dict:
+                raise ValueError(
+                    "The input type must be dict when args_schema is specified."
+                )
+            self.args_schema.validate_args(input)
+        return self._run(**input, **self.special_params)
+
+    async def arun(self, input: Any) -> str:
+        if self.args_schema != None:
+            if type(input) != dict:
+                raise ValueError(
+                    "The input type must be dict when args_schema is specified."
+                )
+            self.args_schema.validate_args(input)
+        return await self._arun(**input, **self.special_params)
+
+    def generate_schema(self):
+        if not self.args_schema:
+            return {
+                "type": "function",
+                "description": self.description,
+                "function": {
+                    "name": self.name,
+                    "parameters": {
+                        "type": "object",
+                        "name": "input",
+                        "required": ["input"],
+                    },
+                },
+            }
+        else:
+            properties, required = self.args_schema.generate_schema()
+            return {
+                "type": "function",
+                "function": {
+                    "name": self.name,
+                    "description": self.description,
+                    "parameters": {
+                        "type": "object",
+                        "properties": properties,
+                        "required": required,
+                    },
+                },
+            }
+
+
+class BaseModelTool(BaseTool, ABC):
+    # data_handler: Optional[SQLDataHandler]
+
+    def visual_prompting(
+        self,
+        image: Image.Image,
+        annotation: List[Target],
+        prompting_type: str = "label_on_img",
+        include_labels: Union[List, set, tuple] = None,
+        exclude_labels: Union[List, set, tuple] = None,
+    ) -> List[Image.Image]:
+        annotator = Annotator(image)
+        for obj in annotation:
+            if (exclude_labels is not None and obj.label in exclude_labels) or (
+                include_labels is not None and obj.label not in include_labels
+            ):
+                continue
+            if obj.bbox:
+                annotator.box_label(obj.bbox, obj.label, color="red")
+            # TODO: Add polygon support
+        return annotator.result()
+
+    def infer(self, images: List[Image.Image], kwargs) -> List[List[Target]]:
+        """The model inference step. Only support OD type detection.
+
+        Args:
+            images (List[Image.Image]): The list of input images. Image should be PIL Image object.
+            kwargs (dict): The additional arguments for the model.
+
+        Returns:
+            List[List[Target]]: The detection results.
+        """
+
+    def ainfer(self, images: List[Image.Image], kwargs) -> List[List[Target]]:
+        """The async version of model inference step. Only support OD type detection.
+
+        Args:
+            images (List[Image.Image]): The list of input images. Image should be PIL Image object.
+            kwargs (dict): The additional arguments for the model.
+
+        Returns:
+            List[List[Target]]: The detection results.
+        """
+
+
+class MemoryTool(BaseTool):
+    memory_handler: Optional[SQLDataHandler]
+
+    def generate_schema(self) -> dict:
+        """Generate the data table schema in dict format.
+
+        Returns:
+            dict: The data table schema. Including the table name, and the name, data type and additional information of each column.
+        """
+        table = self.memory_handler.table
+        schema = {"table_name": table.__tablename__, "columns": []}
+        for column in table.__table__.columns:
+            schema["columns"].append(
+                {
+                    "name": column.name,
+                    "type": column.type.__visit_name__,
+                    "info": column.info,
+                }
+            )
+        return schema
+
+    def generate_prompt(self):
+        pass
+
+    def _run(self):
+        self.memory_handler.execute_sql()
+
+    async def _arun(self):
+        self.memory_handler.execute_sql()
diff --git a/omagent_core/tool_system/manager.py b/omagent_core/tool_system/manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..0ee03d6dea0eeb9eb7b352e587451282f60f5c3a
--- /dev/null
+++ b/omagent_core/tool_system/manager.py
@@ -0,0 +1,379 @@
+import json
+from pathlib import Path
+from typing import Any, Dict, List, Optional, Union
+
+import yaml
+from omagent_core.base import BotBase
+from omagent_core.models.llms.base import BaseLLM, BaseLLMBackend
+from omagent_core.models.llms.prompt.prompt import PromptTemplate
+from omagent_core.models.llms.schemas import Message
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+from pydantic import Field, field_validator
+
+from .base import BaseTool
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+
+class ToolManager(BaseLLMBackend):
+    tools: Dict[str, BaseTool] = Field(
+        default=registry.mapping["tool"], validate_default=True
+    )
+    llm: Optional[BaseLLM] = Field(default=None, validate_default=True)
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    @field_validator("tools", mode="before")
+    @classmethod
+    def init_tools(cls, tools: Union[List, Dict]) -> Dict[str, BaseTool]:
+        if isinstance(tools, dict):
+            for key, value in tools.items():
+                if isinstance(value, type) and issubclass(value, BaseTool):
+                    tools[key] = value = value()
+                elif isinstance(value, dict):
+                    tools[key] = value = registry.get_tool(key)(**value)
+                elif not isinstance(value, BaseTool):
+                    raise ValueError(
+                        "The tool must be an instance of a sub class of BaseTool, not {}".format(
+                            type(value)
+                        )
+                    )
+                if key != value.name:
+                    raise ValueError(
+                        "The tool name {} not match with the tool {}.".format(
+                            key, value.name
+                        )
+                    )
+            return tools
+        elif isinstance(tools, list):
+            init_tools = {}
+            for tool in tools:
+                if isinstance(tool, str):
+                    t = registry.get_tool(tool)
+                    if isinstance(t, BaseTool):
+                        init_tools[tool] = t
+                    elif isinstance(t, type) and issubclass(t, BaseTool):
+                        init_tools[tool] = t()
+                    else:
+                        raise ValueError("Invalid tool type {}".format(type(t)))
+                elif isinstance(tool, dict):
+                    t = registry.get_tool(tool["name"])
+                    if isinstance(t, type) and issubclass(t, BaseTool):
+                        init_tools[tool["name"]] = t(**tool)
+                    else:
+                        raise ValueError("Invalid tool type {}".format(type(t)))
+                elif isinstance(tool, BaseTool):
+                    init_tools[tool.name] = tool
+                else:
+                    raise ValueError("Invalid tool type {}".format(type(tool)))
+            return init_tools
+        else:
+            raise ValueError(
+                "Wrong tools type {}, should be list or dict in ToolManager".format(
+                    type(tools)
+                )
+            )
+
+    def model_post_init(self, __context: Any) -> None:
+        for _, attr_value in self.__dict__.items():
+            if isinstance(attr_value, BotBase):
+                attr_value._parent = self
+        for tool in self.tools.values():
+            tool._parent = self
+
+    @property
+    def workflow_instance_id(self) -> str:
+        if hasattr(self, "_parent"):
+            return self._parent.workflow_instance_id
+        return None
+
+    @workflow_instance_id.setter
+    def workflow_instance_id(self, value: str):
+        if hasattr(self, "_parent"):
+            self._parent.workflow_instance_id = value
+
+    def add_tool(self, tool: BaseTool):
+        self.tools[tool.name] = tool
+
+    def tool_names(self) -> List:
+        return list(self.tools.keys())
+
+    def generate_prompt(self):
+        prompt = ""
+        for index, (name, tool) in enumerate(self.tools.items()):
+            prompt += f"{index + 1}. {name}: {tool.description}\n"
+        return prompt
+
+    def generate_schema(self, style: str = "gpt"):
+        if style == "gpt":
+            return [tool.generate_schema() for tool in self.tools.values()]
+        else:
+            raise ValueError("Only support gpt style tool selection schema")
+
+    def execute(self, tool_name: str, args: Union[str, dict]):
+        if tool_name not in self.tools:
+            raise KeyError(f"The tool {tool_name} is invalid, not in the tool list.")
+        tool = self.tools.get(tool_name)
+        if type(args) is str:
+            try:
+                args = json.loads(args)
+            except Exception as error:
+                if self.llm is not None:
+                    try:
+                        args = self.dynamic_json_fixs(
+                            args, tool.generate_schema(), [], str(error)
+                        )
+                        args = json.loads(args)
+                    except:
+                        raise ValueError(
+                            "The args for tool execution is not a valid json string and can not be fixed. [{}]".format(
+                                args
+                            )
+                        )
+
+                else:
+                    raise ValueError(
+                        "The args for tool execution is not a valid json string. [{}]".format(
+                            args
+                        )
+                    )
+        if tool.args_schema != None:
+            args = tool.args_schema.validate_args(args)
+        return tool.run(args)
+
+    async def aexecute(self, tool_name: str, args: Union[str, dict]):
+        if tool_name not in self.tools:
+            raise KeyError(f"The tool {tool_name} is invalid, not in the tool list.")
+        tool = self.tools.get(tool_name)
+        if type(args) is str:
+            try:
+                args = json.loads(args)
+            except Exception as error:
+                if self.llm is not None:
+                    try:
+                        args = self.dynamic_json_fixs(
+                            args, tool.generate_schema(), [], str(error)
+                        )
+                        args = json.loads(args)
+                    except:
+                        raise ValueError(
+                            "The args for tool execution is not a valid json string and can not be fixed. [{}]".format(
+                                args
+                            )
+                        )
+
+                else:
+                    raise ValueError(
+                        "The args for tool execution is not a valid json string. [{}]".format(
+                            args
+                        )
+                    )
+        if tool.args_schema != None:
+            args = tool.args_schema.validate_args(args)
+        return await tool.arun(args)
+
+    def dynamic_json_fixs(
+        self,
+        broken_json,
+        function_schema,
+        messages: list = [],
+        error_message: str = None,
+    ):
+        logging.warning(
+            "Schema Validation for Function call {} failed, trying to fix it...".format(
+                function_schema["name"]
+            )
+        )
+        messages = [
+            *messages,
+            {
+                "role": "system",
+                "content": "\n".join(
+                    [
+                        "Your last function call result in error",
+                        "--- Error ---",
+                        error_message,
+                        "Your task is to fix all errors exist in the Broken Json String to make the json validate for the schema in the given function, and use new string to call the function again.",
+                        "--- Notice ---",
+                        "- You need to carefully check the json string and fix the errors or adding missing value in it.",
+                        "- Do not give your own opinion or imaging new info or delete exisiting info!",
+                        "- Make sure the new function call does not contains information about this fix task!",
+                        "--- Broken Json String ---",
+                        broken_json,
+                        "Start!",
+                    ]
+                ),
+            },
+        ]
+        fix_res = self.llm.generate(
+            records=[Message(**item) for item in messages], tool_choice=function_schema
+        )
+        return fix_res["choices"][0]["message"]["tool_calls"][0]["function"][
+            "arguments"
+        ]
+
+    @classmethod
+    def from_file(cls, file: Union[str, Path]):
+        if type(file) is str:
+            file = Path(file)
+        elif type(file) is not Path:
+            raise ValueError("Only support str or pathlib.Path")
+        if not file.exists():
+            raise FileNotFoundError("The file {} is not exists.".format(file))
+        if file.suffix == ".json":
+            config = json.load(open(file, "r"))
+        elif file.suffix in (".yaml", ".yml"):
+            config = yaml.load(open(file, "r"), Loader=yaml.FullLoader)
+        else:
+            raise ValueError("Only support json or yaml file.")
+
+        return cls(**config)
+
+    def execute_task(self, task, related_info="", function=None):
+        if self.llm == None:
+            raise ValueError(
+                "The execute_task method requires the llm field to be initialized."
+            )
+        chat_complete_res = self.infer(
+            [{"task": task, "related_info": related_info}],
+            tools=self.generate_schema(),
+        )[0]
+        logging.info(f"ToolManager execute_task chat_complete_res: {chat_complete_res}")
+        content = chat_complete_res["choices"][0]["message"].get("content")
+        tool_calls = chat_complete_res["choices"][0]["message"].get("tool_calls")
+        if not tool_calls:
+            toolcall_failed_structure = {
+                "status": "failed",
+                "content": content,
+            }
+            return "failed", content
+        else:
+            tool_calls = [tool_calls[0]]
+            toolcall_structure = {
+                "name": tool_calls[0]["function"]["name"],
+                "arguments": json.loads(tool_calls[0]["function"]["arguments"]),
+            }
+            self.callback.info(
+                agent_id=self.workflow_instance_id,
+                progress=f"Conqueror",
+                message=f'Tool {toolcall_structure["name"]} executing. Arguments: {toolcall_structure["arguments"]}',
+            )
+            tool_execution_res = []
+            try:
+                for each_tool_call in tool_calls:
+                    result = self.execute(
+                        each_tool_call["function"]["name"],
+                        each_tool_call["function"]["arguments"],
+                    )
+                    tool_execution_res.append(result)
+                toolcall_structure = {
+                    "status": "success",
+                    "tool_use": list(
+                        set(
+                            [
+                                each_tool_call["function"]["name"]
+                                for each_tool_call in tool_calls
+                            ]
+                        )
+                    ),
+                    "argument": [
+                        each_tool_call["function"]["arguments"]
+                        for each_tool_call in tool_calls
+                    ],
+                }
+                return "success", tool_execution_res
+            except ValueError as error:
+                toolcall_failed_structure = {
+                    "status": "failed",
+                    "tool_use": list(
+                        set(
+                            [
+                                each_tool_call["function"]["name"]
+                                for each_tool_call in tool_calls
+                            ]
+                        )
+                    ),
+                    "argument": [
+                        each_tool_call["function"]["arguments"]
+                        for each_tool_call in tool_calls
+                    ],
+                    "error": str(error),
+                }
+                return "failed", str(error)
+
+    async def aexecute_task(self, task, related_info=None, function=None):
+        if self.llm == None:
+            raise ValueError(
+                "The execute_task method requires the llm field to be initialized."
+            )
+        chat_complete_res = await self.ainfer(
+            [{"task": task, "related_info": list(related_info.keys())}],
+            tools=self.generate_schema(),
+        )[0]
+        logging.info(f"ToolManager aexecute_task chat_complete_res: {chat_complete_res}")
+        content = chat_complete_res["choices"][0]["message"].get("content")
+        tool_calls = chat_complete_res["choices"][0]["message"].get("tool_calls")
+        if not tool_calls:
+            toolcall_failed_structure = {
+                "status": "failed",
+                "content": content,
+            }
+            return "failed", content
+        else:
+            toolcall_structure = {
+                "name": tool_calls[0]["function"]["name"],
+                "arguments": json.loads(tool_calls[0]["function"]["arguments"]),
+            }
+            tool_execution_res = []
+            try:
+                for each_tool_call in tool_calls:
+                    tool_execution_res.append(
+                        await self.aexecute(
+                            each_tool_call["function"]["name"],
+                            each_tool_call["function"]["arguments"],
+                        )
+                    )
+
+                toolcall_structure = {
+                    "status": "success",
+                    "tool_use": list(
+                        set(
+                            [
+                                each_tool_call["function"]["name"]
+                                for each_tool_call in tool_calls
+                            ]
+                        )
+                    ),
+                    "argument": [
+                        eval(each_tool_call["function"]["arguments"])
+                        for each_tool_call in tool_calls
+                    ],
+                }
+                return "success", tool_execution_res
+            except ValueError as error:
+                toolcall_failed_structure = {
+                    "status": "failed",
+                    "tool_use": list(
+                        set(
+                            [
+                                each_tool_call["function"]["name"]
+                                for each_tool_call in tool_calls
+                            ]
+                        )
+                    ),
+                    "argument": [
+                        each_tool_call["function"]["arguments"]
+                        for each_tool_call in tool_calls
+                    ],
+                    "error": str(error),
+                }
+                return "failed", str(error)
diff --git a/omagent_core/tool_system/sys_prompt.prompt b/omagent_core/tool_system/sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..c1e8e3ff85213caa0cab9b046079349e54be07eb
--- /dev/null
+++ b/omagent_core/tool_system/sys_prompt.prompt
@@ -0,0 +1,5 @@
+You are an intelligent agent, your job is to select the appropriate tools from the ones provided by the system to complete the task assigned to you.
+If none of the tools at your disposal can complete the task assigned to you perfectly, you will provide the reasons.
+
+*** Important Notice ***
+Ensure that the tool you use can complete your tasks comprehensively, without missing any details or subtasks. 
\ No newline at end of file
diff --git a/omagent_core/tool_system/tools/__init__.py b/omagent_core/tool_system/tools/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/tool_system/tools/calculator/calculator.py b/omagent_core/tool_system/tools/calculator/calculator.py
new file mode 100644
index 0000000000000000000000000000000000000000..0911bc0de6efe4e439f519f56afa0d232b14a963
--- /dev/null
+++ b/omagent_core/tool_system/tools/calculator/calculator.py
@@ -0,0 +1,50 @@
+import os
+import subprocess
+
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseTool
+
+ARGSCHEMA = {
+    "code": {
+        "type": "string",
+        "description": "Python code block to be executed of mathematical calculation derivation.",
+    }
+}
+
+
+@registry.register_tool()
+class Calculator(BaseTool):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = (
+        "Calculator tool for executing all mathematical calculation. Final result must be wrapped by print function and output to stdout."
+    )
+
+    def _run(self, code: str = None, filename: str = "calculator_code.py") -> dict:
+        if code:
+            with open(filename, "w") as f:
+                f.write(code)
+        command = f"python {filename}"
+        try:
+            exec_proc = subprocess.Popen(
+                command,
+                shell=True,
+                stderr=subprocess.PIPE,
+                stdout=subprocess.PIPE,
+                stdin=subprocess.PIPE,
+                cwd=".",
+            )
+            stdout, stderr = exec_proc.communicate(timeout=30)
+
+        except Exception as e:
+            raise ValueError(e)
+
+        if stderr:
+            raise ValueError(f"{code}\nExecute error:\n {stderr.decode()}")
+        result = {
+            "ReturnCode": exec_proc.returncode,
+            "Error": stderr.decode() if stderr else None,
+            "Output": stdout.decode() if stdout else None,
+            "absolute_filename": os.path.abspath(filename),
+        }
+
+        return result
diff --git a/omagent_core/tool_system/tools/code_interpreter/code_interpreter.py b/omagent_core/tool_system/tools/code_interpreter/code_interpreter.py
new file mode 100644
index 0000000000000000000000000000000000000000..d12e4b5bf379d007cc8435947125673445dbe5a1
--- /dev/null
+++ b/omagent_core/tool_system/tools/code_interpreter/code_interpreter.py
@@ -0,0 +1,87 @@
+import asyncio
+import os
+import subprocess
+
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseTool
+
+ARGSCHEMA = {
+    "code": {"type": "string", "description": "Code block to be executed"},
+    "command": {
+        "type": "string",
+        "description": "Command to be executed, need to be python filename, e.g. python llm_code.py",
+        "required": False,
+    },
+    "filename": {
+        "type": "string",
+        "description": "Filename of the code block",
+        "required": False,
+    },
+}
+
+
+@registry.register_tool()
+class CodeInterpreter(BaseTool):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = (
+        "Python code must be run via this tool, it can be simple code or complex code like deploying web server. Contains libs like numpy, pandas, matplotlib, sklearn, etc. You must print the result to stdout."
+    )
+
+    def _run(
+        self, code: str = None, command: str = None, filename: str = "llm_code.py"
+    ) -> dict:
+        if code:
+            with open(filename, "w") as f:
+                f.write(code)
+        command = command or f"python {filename}"
+        try:
+            exec_proc = subprocess.Popen(
+                command,
+                shell=True,
+                stderr=subprocess.PIPE,
+                stdout=subprocess.PIPE,
+                stdin=subprocess.PIPE,
+                cwd=".",
+            )
+            stdout, stderr = exec_proc.communicate(timeout=30)
+
+        except Exception as e:
+            raise ValueError(e)
+
+        if stderr:
+            raise ValueError(f"{code}\nExecute error:\n {stderr.decode()}")
+        result = {
+            "ReturnCode": exec_proc.returncode,
+            "Error": stderr.decode() if stderr else None,
+            "Output": stdout.decode() if stdout else None,
+            "absolute_filename": os.path.abspath(filename),
+        }
+
+        return result
+
+    async def _arun(
+        self, code: str = None, command: str = None, filename: str = "llm_code.py"
+    ) -> str:
+        if code:
+            with open(filename, "w") as f:
+                f.write(code)
+
+        command = command or f"python {filename}"
+        exec_proc = await asyncio.create_subprocess_shell(
+            command,
+            stderr=asyncio.subprocess.PIPE,
+            stdout=asyncio.subprocess.PIPE,
+            stdin=asyncio.subprocess.PIPE,
+            cwd=".",
+        )
+
+        stdout, stderr = await asyncio.wait_for(exec_proc.communicate(), timeout=10)
+
+        result = {
+            "ReturnCode": exec_proc.returncode,
+            "Error": stderr.decode() if stderr else None,
+            "Output": stdout.decode() if stdout else None,
+            "absolute_filename": os.path.abspath(filename),
+        }
+
+        return result
diff --git a/omagent_core/tool_system/tools/file/file_read.py b/omagent_core/tool_system/tools/file/file_read.py
new file mode 100644
index 0000000000000000000000000000000000000000..b99fbc318e5147e806dc99eade778444da7930d8
--- /dev/null
+++ b/omagent_core/tool_system/tools/file/file_read.py
@@ -0,0 +1,24 @@
+import os
+
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseTool
+
+ARGSCHEMA = {"file_path": {"type": "string", "description": "The file path to read."}}
+
+
+@registry.register_tool()
+class ReadFileContent(BaseTool):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = (
+        "Reads data from a file. It can be check whether the file exists or not."
+    )
+
+    def _run(self, file_path: str = None) -> str:
+        if not file_path:
+            return "Please specify the file path."
+        if not os.path.exists(file_path):
+            return "The file path does not exist."
+        if not os.path.isfile(file_path):
+            return "The file path is not a file."
+        with open(file_path, "r") as f:
+            return f.read()
diff --git a/omagent_core/tool_system/tools/file/file_write.py b/omagent_core/tool_system/tools/file/file_write.py
new file mode 100644
index 0000000000000000000000000000000000000000..c32d02caa6273404f6e41a74e9499795fa8e26fc
--- /dev/null
+++ b/omagent_core/tool_system/tools/file/file_write.py
@@ -0,0 +1,25 @@
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseTool
+
+ARGSCHEMA = {
+    "file_path": {"type": "string", "description": "The file path to write."},
+    "content": {"type": "string", "description": "The content to write."},
+    "file_mode": {
+        "type": "string",
+        "description": "The file mode to write.",
+        "required": False,
+    },
+}
+
+
+@registry.register_tool()
+class WriteFileContent(BaseTool):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = "Write data to a file, replacing the file if it already exists."
+
+    def _run(self, file_path: str, content: str, file_mode: str = "w") -> str:
+        if not file_path:
+            return "Please specify the file path."
+        with open(file_path, file_mode) as f:
+            f.write(content)
+        return f"Write file successfully in {file_path}."
diff --git a/omagent_core/tool_system/tools/ovd/ovd_tool.py b/omagent_core/tool_system/tools/ovd/ovd_tool.py
new file mode 100755
index 0000000000000000000000000000000000000000..d6827dc30b1cbce1dae3ee8eea1ed3f581e8c3dc
--- /dev/null
+++ b/omagent_core/tool_system/tools/ovd/ovd_tool.py
@@ -0,0 +1,123 @@
+from pathlib import Path
+from typing import List
+
+import requests
+from PIL import Image
+from scenedetect import FrameTimecode
+
+from ....models.od.schemas import Target
+from ....utils.general import encode_image
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseModelTool
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+ARGSCHEMA = {
+    "timestamps": {
+        "type": "string",
+        "description": "Timestamp(seconds) of frames that need to be object detected, split by comma.",
+        "required": True,
+    },
+    "labels": {
+        "type": "string",
+        "description": "Labels the object detection tool will use to detect objects in the image, split by comma.",
+        "required": True,
+    },
+}
+
+
+@registry.register_tool()
+class ObjectDetection(BaseModelTool):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = (
+        "Object detection tool, which can detect any objects and add visual prompting(bounding box and label) to the image."
+        "Tasks like object counting, specific object detection, etc. must use this tool."
+    )
+    ovd_endpoint: str = ""
+    model_id: str = "OmDet-Turbo_tiny_SWIN_T"
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        protected_namespaces = ()
+
+    def _run(self, timestamps: str, labels: str) -> str:
+        if self.ovd_endpoint is None or self.ovd_endpoint == "":
+            raise ValueError("ovd_endpoint is required.")
+        timestamps = timestamps.split(",")
+        imgs_pil = []
+        for each_time_stamp in timestamps:
+            if (
+                self.stm.image_cache.get(
+                    f"<image_timestamp-{float(each_time_stamp)}>", None
+                )
+                is None
+            ):
+                frames, time_stamps = self.stm.video.get_video_frames(
+                    (
+                        FrameTimecode(
+                            timecode=float(each_time_stamp),
+                            fps=self.stm.video.stream.frame_rate,
+                        ),
+                        FrameTimecode(
+                            timecode=float(each_time_stamp) + 1,
+                            fps=self.stm.video.stream.frame_rate,
+                        ),
+                    ),
+                    self.stm.video.stream.frame_rate,
+                )
+                [
+                    self.stm.image_cache.update(
+                        {f"<image_timestamp-{each_img_name}>": each_frame}
+                    )
+                    for each_frame, each_img_name in zip(frames, time_stamps)
+                ]
+                # timestamps = [f'<image_timestamp-{each_img_name}' for each_img_name in time_stamps]
+                imgs_pil = [each_frame for each_frame in frames]
+            else:
+                imgs_pil.append(
+                    self.stm.image_cache[f"<image_timestamp-{float(each_time_stamp)}>"]
+                )
+
+        infer_targets = self.infer(imgs_pil, {"labels": labels.split(",")})
+        for img_name, img, infer_target in zip(timestamps, imgs_pil, infer_targets):
+            self.stm.image_cache[f"<image_timestamp-{img_name}>"] = (
+                self.visual_prompting(img, infer_target)
+            )
+
+        return f"OVD tool has detected objects in timestamps of {timestamps} and update image."
+
+    async def _arun(self, timestamps: str, labels: str) -> str:
+        return self._run(timestamps, labels)
+
+    def infer(self, images: List[Image.Image], kwargs) -> List[List[Target]]:
+        labels = kwargs.get("labels", [])
+        ovd_payload = {
+            "model_id": self.model_id,
+            "data": [encode_image(img) for img in images],
+            "src_type": "base64",
+            "task": f"Detect {','.join(labels)}.",
+            "labels": labels,
+            "threshold": 0.3,
+        }
+        res = requests.post(self.ovd_endpoint, json=ovd_payload)
+        if res.status_code != 200:
+            raise ValueError(
+                f"OVD tool failed to detect objects in the images. {res.text}"
+            )
+        res = res.json()
+        targets = []
+        for img in res["objects"]:
+            current_img_targets = []
+            for bbox in img:
+                x0 = bbox["xmin"]
+                y0 = bbox["ymin"]
+                x1 = bbox["xmax"]
+                y1 = bbox["ymax"]
+                conf = bbox["conf"]
+                label = bbox["label"]
+                current_img_targets.append(
+                    Target(bbox=[x0, y0, x1, y1], conf=conf, label=label)
+                )
+            targets.append(current_img_targets)
+        return targets
diff --git a/omagent_core/tool_system/tools/shell/shell.py b/omagent_core/tool_system/tools/shell/shell.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce56cf47baab4c4e748863994c7e6794c283ec8b
--- /dev/null
+++ b/omagent_core/tool_system/tools/shell/shell.py
@@ -0,0 +1,37 @@
+import subprocess
+
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseTool
+
+ARGSCHEMA = {
+    "command": {
+        "type": "string",
+        "description": "The shell command to run, you are authorized to modify system's path to fix some problems.",
+    }
+}
+
+
+@registry.register_tool()
+class ShellTool(BaseTool):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = "Run shell command."
+
+    def _run(self, command) -> dict:
+        """Run shell command."""
+        result = subprocess.run(command, shell=True, capture_output=True, text=True)
+
+        return_code = result.returncode
+
+        output = result.stdout.strip()
+
+        error = result.stderr.strip()
+
+        if error:
+            raise ValueError(error)
+        result = {
+            "return_code": return_code,
+            "output": output,
+            "error": error,
+            "shell_command": command,
+        }
+        return result
diff --git a/omagent_core/tool_system/tools/utils.py b/omagent_core/tool_system/tools/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9e1c2a3755194dee1418f83ce8f20adaf3d87653
--- /dev/null
+++ b/omagent_core/tool_system/tools/utils.py
@@ -0,0 +1,9 @@
+import platform
+
+
+def get_platform() -> str:
+    """Get platform."""
+    system = platform.system()
+    if system == "Darwin":
+        return "MacOS"
+    return system
diff --git a/omagent_core/tool_system/tools/web_search/search.py b/omagent_core/tool_system/tools/web_search/search.py
new file mode 100644
index 0000000000000000000000000000000000000000..412a885220015fb93309fad1019cad5960a44afb
--- /dev/null
+++ b/omagent_core/tool_system/tools/web_search/search.py
@@ -0,0 +1,210 @@
+from pathlib import Path
+from typing import Any, List, Optional, Union
+
+import httpx
+import requests
+from bs4 import BeautifulSoup
+from duckduckgo_search import DDGS
+from pydantic import Field, field_validator
+
+from ....models.llms.base import BaseLLM, BaseLLMBackend
+from ....models.llms.openai_gpt import OpenaiGPTLLM
+from ....models.llms.prompt import PromptTemplate
+from ....utils.logger import logging
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseTool
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+ARGSCHEMA = {
+    "search_query": {"type": "string", "description": "The search query."},
+    "goals_to_browse": {
+        "type": "string",
+        "description": "What's you want to find on the website returned by search. If you need more details, request it in here. Examples: 'What is latest news about deepmind?', 'What is the main idea of this article?'",
+    },
+    "region": {
+        "type": "string",
+        "description": "The region code of the search, default to `en-US`. Available regions: `en-US`, `zh-CN`, `ja-JP`, `de-DE`, `fr-FR`, `en-GB`.",
+        "required": True,
+    },
+    "num_results": {
+        "type": "integer",
+        "description": "The page number of results to return, default is 1, maximum is 3.",
+        "required": True,
+    },
+}
+
+
+@registry.register_tool()
+class WebSearch(BaseTool, BaseLLMBackend):
+    """Web Environment providing web interface and browsering."""
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = (
+        "Searches the web for multiple queries or mimic multiple API calls."
+    )
+    prompts: List[PromptTemplate] = Field(
+        default=[
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("search_sys_prompt.prompt"), role="system"
+            ),
+            PromptTemplate.from_file(
+                CURRENT_PATH.joinpath("search_user_prompt.prompt"), role="user"
+            ),
+        ]
+    )
+
+    def __init__(__pydantic_self__, **data: Any) -> None:
+        super().__init__(**data)
+        __pydantic_self__.client = httpx.AsyncClient(
+            headers={
+                "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
+            },
+            verify=False,
+            timeout=30.0,
+            http2=True,
+        )
+
+    bing_api_key: Optional[str]
+    bing_endpoint: str = "https://api.bing.microsoft.com/v7.0/search"
+
+    @field_validator("bing_api_key")
+    @classmethod
+    def api_key_validator(cls, bing_api_key: Union[str, None]) -> Union[str, None]:
+        if bing_api_key == None:
+            logging.warning("Bing API key is not provided, rollback to duckduckgo.")
+        return bing_api_key
+
+    def _check_url_valid(self, url: str):
+        local_prefixes = [
+            "file:///",
+            "file://127.0.0.1",
+            "file://localhost",
+            "http://localhost",
+            "https://localhost",
+            "http://2130706433",
+            "https://2130706433",
+            "http://127.0.0.1",
+            "https://127.0.0.1",
+            "https://0.0.0.0",
+            "http://0.0.0.0",
+            "http://0000",
+            "https://0000",
+        ]
+        if any(url.startswith(prefix) for prefix in local_prefixes):
+            raise ValueError(f"URL {url} is a local url, blocked!")
+        if not (url.startswith("http") or url.startswith("file")):
+            raise ValueError(
+                f"URL {url} is not a http or https url, please give a valid url!"
+            )
+
+    def _run(
+        self, search_query: str, goals_to_browse: str, region: str = None, num_results=3
+    ) -> List[str]:
+        """Search with search tools and browse the website returned by search. Note some websites may not be accessable due to network error.
+
+        :param string search_query: The search query.
+        :param string goals_to_browse: What's you want to find on the website returned by search. If you need more details, request it in here. Examples: 'What is latest news about deepmind?', 'What is the main idea of this article?'
+        :param string? region: The region code of the search, default to `en-US`. Available regions: `en-US`, `zh-CN`, `ja-JP`, `de-DE`, `fr-FR`, `en-GB`.
+        :return string: The results of the search.
+        """
+        # self.callback.info(
+        #     agent_id=self.workflow_instance_id,
+        #     progress=f"Conqueror",
+        #     message=f'Searching for "{search_query}".',
+        # )
+        if region is None:
+            region = "en-US"
+        if self.bing_api_key is None:
+            pages = [
+                {"name": ret["title"], "snippet": ret["body"], "url": ret["href"]}
+                for ret in DDGS().text(search_query, region="wt-wt")
+            ]
+
+        else:
+            try:
+                result = requests.get(
+                    self.bing_endpoint,
+                    headers={"Ocp-Apim-Subscription-Key": self.bing_api_key},
+                    params={"q": search_query, "mkt": region},
+                    timeout=10,
+                )
+
+                result.raise_for_status()
+                result = result.json()
+                pages = result["webPages"]["value"]
+            except Exception as e:
+                logging.error(f"Bing search failed: {e}")
+                return [
+                    {
+                        "name": "",
+                        "snippet": "",
+                        "url": "",
+                        "page": "",
+                    }
+                ]
+
+        search_results = []
+
+        for idx in range(min(len(pages), num_results)):
+            try:
+                page = self.browse_website(pages[idx]["url"], goals_to_browse)
+                page = self.simple_infer(search_results=page)["choices"][0]["message"][
+                    "content"
+                ]
+            except httpx.HTTPStatusError as e:
+                page = e.response.text
+            except Exception as e:
+                page = str(e)
+
+            message = {
+                "name": pages[idx]["name"],
+                "snippet": pages[idx]["snippet"],
+                "url": pages[idx]["url"],
+                "page": page,
+            }
+            search_results.append(message)
+
+        return search_results
+
+    def browse_website(self, url: str, goals_to_browse: str) -> str:
+        """Give a http or https url to browse a website and return the summarize text. Note some websites may not be accessable due to network error. This tool only return the content of give url and cannot provide any information need interaction with the website.
+
+        :param string url: The realworld Uniform Resource Locator (web address) to scrape text from. Never provide something like "<URL of the second news article>", give real url!!! Example: 'https://www.deepmind.com/'
+        :param string goals_to_browse: The goals for browse the given `url` (e.g. what you want to find on webpage.). If you need more details, request it in here.
+        :return string: The content of the website, with formatted text.
+        """
+        # self._check_url_valid(url)
+        headers = {
+            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36"
+        }
+        res = requests.get(url, headers=headers, timeout=30)
+        if res.status_code in [301, 302, 307, 308]:
+            res = requests.get(res.headers["location"])
+        else:
+            res.raise_for_status()
+
+        soup = BeautifulSoup(res.text, "html.parser")
+        text = soup.get_text()
+        lines = (line.strip() for line in text.splitlines())
+        chunks = (phrase.strip() for line in lines for phrase in line.split("  "))
+        text = "\n".join(chunk for chunk in chunks if chunk)
+
+        links = soup.find_all("a")
+        if len(links) > 0:
+            text += "\n\nLinks:\n"
+            for link in links:
+                if link.string != None and link.get("href") != None:
+                    striped_link_string = link.string.strip()
+                    if striped_link_string != "" and link.get("href").startswith(
+                        "http"
+                    ):
+                        text += f"{striped_link_string} ({link.get('href')})\n"
+
+        return text
diff --git a/omagent_core/tool_system/tools/web_search/search_sys_prompt.prompt b/omagent_core/tool_system/tools/web_search/search_sys_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..24309cc967f82ae2934fb4af11b6dc824c8952be
--- /dev/null
+++ b/omagent_core/tool_system/tools/web_search/search_sys_prompt.prompt
@@ -0,0 +1,5 @@
+You are an intelligent agent, your job is to remove unnecessary information from search results and extract the main information from them.
+
+
+*** Important Notice ***
+Ensure that information loss does not occur due to compression, and your main responsibility is to remove unnecessary information.
\ No newline at end of file
diff --git a/omagent_core/tool_system/tools/web_search/search_user_prompt.prompt b/omagent_core/tool_system/tools/web_search/search_user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..99f4957a00c941a6f5b2349f0543874033319e00
--- /dev/null
+++ b/omagent_core/tool_system/tools/web_search/search_user_prompt.prompt
@@ -0,0 +1 @@
+Search result: {{search_results}}
\ No newline at end of file
diff --git a/omagent_core/tool_system/tools/web_search/tavily_search.py b/omagent_core/tool_system/tools/web_search/tavily_search.py
new file mode 100644
index 0000000000000000000000000000000000000000..14176cd6e57b9f35440f4ac4d69845c08418c8c9
--- /dev/null
+++ b/omagent_core/tool_system/tools/web_search/tavily_search.py
@@ -0,0 +1,129 @@
+from pathlib import Path
+from typing import Any, Dict, Optional, Union
+
+from pydantic import field_validator
+from tavily import TavilyClient
+
+from ....utils.logger import logging
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseTool
+
+CURRENT_PATH = Path(__file__).parents[0]
+
+ARGSCHEMA = {
+    "search_query": {
+        "type": "string",
+        "description": "The search query, the task need to be done.",
+    },
+    "topic": {
+        "type": "string",
+        "enum": ["general", "news"],
+        "description": "This will optimized the search for the selected topic with tailored and curated information. Default is `general`.",
+    },
+    "include_answer": {
+        "type": "boolean",
+        "description": "Whether to include the conclude answer in the search results. Default is `True`.",
+        "required": True,
+    },
+    "include_images": {
+        "type": "boolean",
+        "description": "Whether to include the images in the search results. Default is `False`.",
+        "required": True,
+    },
+    "include_raw_content": {
+        "type": "boolean",
+        "description": "Whether to include the raw content in the search results. Default is `False`.",
+        "required": False,
+    },
+    "days": {
+        "type": "integer",
+        "description": "The number of days to search for, only available when `topic` is `news`. Default is `3`.",
+        "required": False,
+    },
+    "max_results": {
+        "type": "integer",
+        "description": "The maximum number of results to return. Default is `5`.",
+        "required": True,
+    },
+}
+
+
+@registry.register_tool()
+class TavilyWebSearch(BaseTool):
+    """Web Environment providing web interface and browsering."""
+
+    class Config:
+        """Configuration for this pydantic object."""
+
+        extra = "allow"
+        arbitrary_types_allowed = True
+
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = "Searches the web for a query by tavily."
+    tavily_api_key: Optional[str]
+
+    def __init__(self, **data: Any) -> None:
+        super().__init__(**data)
+        self.tavily_client = TavilyClient(api_key=self.tavily_api_key)
+
+    @field_validator("tavily_api_key")
+    @classmethod
+    def api_key_validator(cls, tavily_api_key: Union[str, None]) -> Union[str, None]:
+        if tavily_api_key == None:
+            raise ValueError("Tavily API key is not provided.")
+        return tavily_api_key
+
+    def _run(
+        self,
+        search_query: str,
+        topic: str,
+        include_answer: bool = True,
+        include_images: bool = False,
+        include_raw_content: bool = False,
+        days: int = 3,
+        max_results=5,
+    ) -> Dict[str, Any]:
+        """
+        Search with search tools and browse the website returned by search. Note some websites may not be accessable due to network error.
+        """
+        # self.callback.info(
+        #     agent_id=self.workflow_instance_id,
+        #     progress=f"Conqueror",
+        #     message=f"Detail of search structure: search_query: {search_query}, topic: {topic}, include_answer: {include_answer}, include_images: {include_images}, include_raw_content: {include_raw_content}, days: {days}, max_results: {max_results}.",
+        # )
+
+        try:
+            search_results = self.tavily_client.search(
+                query=search_query,
+                topic=topic,
+                days=days,
+                max_results=max_results,
+                include_answer=include_answer,
+                include_raw_content=include_raw_content,
+                include_images=include_images,
+            )
+            if include_answer:
+                conclude_answer = search_results.get("answer", "")
+            else:
+                conclude_answer = ""
+            if include_images:
+                search_images = search_results.get("images", [])
+            else:
+                search_images = []
+            results = search_results.get("results", [])
+            if len(results):
+                [each.pop("score") for each in results]
+                if include_raw_content:
+                    [each.pop("raw_content") for each in results]
+            return {
+                "answer": conclude_answer,
+                "images": search_images,
+                "results": results,
+            }
+        except Exception as e:
+            logging.error(f"Tavily search failed: {e}")
+            return {
+                "answer": "",
+                "images": [],
+                "results": [],
+            }
diff --git a/omagent_core/tool_system/user_prompt.prompt b/omagent_core/tool_system/user_prompt.prompt
new file mode 100644
index 0000000000000000000000000000000000000000..e78dc623d04e3588157ccac57277994f3b65105d
--- /dev/null
+++ b/omagent_core/tool_system/user_prompt.prompt
@@ -0,0 +1,2 @@
+Task: {{task}}
+Related info of this task: {{related_info}}
\ No newline at end of file
diff --git a/omagent_core/utils/__init__.py b/omagent_core/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/omagent_core/utils/build.py b/omagent_core/utils/build.py
new file mode 100644
index 0000000000000000000000000000000000000000..e3e987e6f51858a45669a8fe356944e852bd9aee
--- /dev/null
+++ b/omagent_core/utils/build.py
@@ -0,0 +1,108 @@
+import json
+import os
+import re
+from copy import deepcopy
+from distutils.util import strtobool
+from pathlib import Path
+
+import yaml
+
+
+
+def build_from_file(file_path: str):
+    path = Path(file_path)
+    workers_path = path / "workers"
+    if not workers_path.exists():
+        return []
+
+    # init config dict
+    worker_configs = {}
+    other_configs = {}
+    file_names = {}
+
+    for file in path.rglob("*"):
+        if file.suffix not in [".json", ".yaml", ".yml"]:
+            continue
+
+        key = file.name.split(".", 1)[0]
+
+        if key in file_names:
+            raise Exception(
+                f"Duplicate file name [{key}] found:\n"
+                f"File 1: {file_names[key]}\n"
+                f"File 2: {file}"
+            )
+        file_names[key] = file
+
+        try:
+            with open(file, "r") as f:
+                if file.suffix == ".json":
+                    content = json.load(f)
+                else:  # .yaml or .yml
+                    content = yaml.load(f, Loader=yaml.FullLoader)
+        except Exception as e:
+            raise Exception(f"Error loading file {file}: {str(e)}")
+
+        if workers_path in file.parents:
+            worker_configs[key] = content
+        else:
+            other_configs[key] = content
+
+    for conf in worker_configs.values():
+        prep_config(conf, other_configs, [])
+
+    worker_configs_list = []
+    for worker_config in worker_configs.values():
+        if isinstance(worker_config, list):
+            worker_configs_list.extend(worker_config)
+        else:
+            worker_configs_list.append(worker_config)
+
+    return worker_configs_list
+
+
+def prep_config(sub_config: dict | list, config: dict, forbid_keys: list):
+    if isinstance(sub_config, dict):
+        for key, conf in sub_config.items():
+            if isinstance(conf, str):
+                if match := re.search(r"\$\{sub\|([^}]+)\}", conf):
+                    module_key = match.group(1).strip()
+                    if module_key not in config:
+                        raise Exception(
+                            "Incomplete configuration, lack module [{}]".format(
+                                module_key
+                            )
+                        )
+                    elif module_key in forbid_keys:
+                        raise Exception(
+                            "Can't reference submodule recursively. [{}]".format(
+                                module_key
+                            )
+                        )
+                    sub_config[key] = deepcopy(config[module_key])
+                    prep_config(sub_config[key], config, forbid_keys + [module_key])
+                elif match := re.search(r"\$\{env\|([^,}]+)(?:,([^}]+))?\}", conf):
+                    env_key = match.group(1).strip()
+                    default_value = match.group(2)
+                    env_value = os.getenv(env_key)
+                    if env_value:
+                        sub_config[key] = env_value
+                    elif not env_value and default_value:
+                        sub_config[key] = default_value.strip()
+                        if sub_config[key] == "null" or sub_config[key] == "~":
+                            sub_config[key] = None
+                    else:
+                        raise ValueError(
+                            f"Environmental variable {env_key} need to be set."
+                        )
+
+            elif isinstance(conf, dict):
+                prep_config(sub_config[key], config, forbid_keys)
+
+            elif isinstance(conf, list):
+                for i, item in enumerate(conf):
+                    if isinstance(item, dict):
+                        prep_config(sub_config[key][i], config, forbid_keys)
+    elif isinstance(sub_config, list):
+        for item in sub_config:
+            prep_config(item, config, forbid_keys)
diff --git a/omagent_core/utils/container.py b/omagent_core/utils/container.py
new file mode 100644
index 0000000000000000000000000000000000000000..7b5b934185cd6eda6eb85816e81e64d98ddf2d08
--- /dev/null
+++ b/omagent_core/utils/container.py
@@ -0,0 +1,306 @@
+from pathlib import Path
+from typing import Dict, List, Optional, Type
+from threading import Thread
+
+from omagent_core.engine.configuration.aaas_config import AaasConfig
+import yaml
+from omagent_core.engine.configuration.configuration import (TEMPLATE_CONFIG,
+                                                             Configuration)
+from omagent_core.engine.configuration.aaas_config import AAAS_TEMPLATE_CONFIG
+from omagent_core.utils.registry import registry
+from pydantic import BaseModel
+import os
+
+
+class Container:
+    def __init__(self):
+        self._connectors: Dict[str, BaseModel] = {}
+        self._components: Dict[str, BaseModel] = {}
+        self._stm_name: Optional[str] = None
+        self._ltm_name: Optional[str] = None
+        self._callback_name: Optional[str] = None
+        self._input_name: Optional[str] = None
+        self.conductor_config = Configuration()
+        self.aaas_config = AaasConfig()
+
+    def register_connector(
+        self,
+        connector: Type[BaseModel],
+        name: str = None,
+        overwrite: bool = False,
+        **kwargs,
+    ) -> None:
+        """Register a connector"""
+        if name is None:
+            name = connector.__name__
+        if name not in self._connectors or overwrite:
+            self._connectors[name] = connector(**kwargs)
+
+    def get_connector(self, name: str) -> BaseModel:
+        if name not in self._connectors:
+            raise KeyError(f"There is no connector named '{name}' in container.")
+        return self._connectors[name]
+
+    def register_component(
+        self,
+        component: str | Type[BaseModel],
+        name: str = None,
+        config: dict = {},
+        overwrite: bool = False,
+    ) -> None:
+        """Generic component registration method
+
+        Args:
+            component: Component name or class
+            key: The key to save and retrieve component
+            config: Component configuration
+            target_dict: Target dictionary to store component instances
+            component_category: One of the register mapping types, should be provided if component is a string
+        """
+        if isinstance(component, str):
+            component_cls = registry.get_component(component)
+            component_name = component
+            if not component_cls:
+                raise ValueError(f"{component} not found in registry")
+        elif isinstance(component, type) and issubclass(component, BaseModel):
+            component_cls = component
+            component_name = component.__name__
+        else:
+            raise ValueError(f"Invalid component type: {type(component)}")
+
+        if (
+            name in self._components or component_name in self._components
+        ) and not overwrite:
+            return name or component_name
+
+        required_connectors = self._get_required_connectors(component_cls)
+        if required_connectors:
+            for connector, cls_name in required_connectors:
+                if connector not in self._connectors.keys():
+                    connector_cls = registry.get_connector(cls_name)
+                    self.register_connector(connector_cls, connector)
+                config[connector] = self._connectors[connector]
+
+        self._components[name or component_name] = component_cls(**config)
+        return name or component_name
+
+    def get_component(self, component_name: str) -> BaseModel:
+        if component_name not in self._components:
+            raise KeyError(
+                f"There is no component named '{component_name}' in container. You need to register it first."
+            )
+        return self._components[component_name]
+
+    def _get_required_connectors(self, cls: Type[BaseModel]) -> List[str]:
+        required_connectors = []
+        for field_name, field in cls.model_fields.items():
+            if isinstance(field.annotation, type) and issubclass(
+                field.annotation, BaseModel
+            ):
+                required_connectors.append([field_name, field.annotation.__name__])
+        return required_connectors
+
+    @property
+    def components(self) -> Dict[str, BaseModel]:
+        return self._components
+
+    def register_stm(
+        self,
+        stm: str | Type[BaseModel],
+        name: str = None,
+        config: dict = {},
+        overwrite: bool = False,
+    ):
+        if os.getenv("OMAGENT_MODE") == "lite":
+            name = "SharedMemSTM"
+        name = self.register_component(stm, name, config, overwrite)
+        self._stm_name = name
+
+    @property
+    def stm(self) -> BaseModel:
+        if self._stm_name is None:
+            if os.getenv("OMAGENT_MODE") == "lite":
+                self.register_stm("SharedMemSTM")
+                self._stm_name = "SharedMemSTM"
+            else:
+                raise ValueError(
+                    "STM component is not registered. Please use register_stm to register."
+                )   
+
+        return self.get_component(self._stm_name)
+
+    def register_ltm(
+        self,
+        ltm: str | Type[BaseModel],
+        name: str = None,
+        config: dict = {},
+        overwrite: bool = False,
+    ):
+        name = self.register_component(ltm, name, config, overwrite)
+        self._ltm_name = name
+
+    @property
+    def ltm(self) -> BaseModel:
+        if self._ltm_name is None:
+            raise ValueError(
+                "LTM component is not registered. Please use register_ltm to register."
+            )
+        return self.get_component(self._ltm_name)
+
+    def register_callback(
+        self,
+        callback: str | Type[BaseModel],
+        name: str = None,
+        config: dict = {},
+        overwrite: bool = False,
+    ):
+        name = self.register_component(callback, name, config, overwrite)
+        self._callback_name = name
+
+    @property
+    def callback(self) -> BaseModel:
+        if self._callback_name is None:
+            raise ValueError(
+                "Callback component is not registered. Please use register_callback to register."
+            )
+        return self.get_component(self._callback_name)
+
+    def register_input(
+        self,
+        input: str | Type[BaseModel],
+        name: str = None,
+        config: dict = {},
+        overwrite: bool = False,
+    ):
+        name = self.register_component(input, name, config, overwrite)
+        self._input_name = name
+
+    @property
+    def input(self) -> BaseModel:
+        if self._input_name is None:
+            raise ValueError(
+                "Input component is not registered. Please use register_input to register."
+            )
+        return self.get_component(self._input_name)
+
+    def compile_config(
+        self, output_path: Path, description: bool = True, env_var: bool = True
+    ) -> None:
+        if (output_path / "container.yaml").exists():
+            print("container.yaml already exists, skip compiling")
+            config = yaml.load(
+                open(output_path / "container.yaml", "r"), Loader=yaml.FullLoader
+            )
+            return config
+
+        config = {
+            "conductor_config": TEMPLATE_CONFIG,
+            "aaas_config": AAAS_TEMPLATE_CONFIG,
+            "connectors": {},
+            "components": {},
+        }
+        exclude_fields = [
+            "_parent",
+            "component_stm",
+            "component_ltm",
+            "component_callback",
+            "component_input",
+        ]
+        for name, connector in self._connectors.items():
+            config["connectors"][name] = connector.__class__.get_config_template(
+                description=description, env_var=env_var, exclude_fields=exclude_fields
+            )
+        exclude_fields.extend(self._connectors.keys())
+        for name, component in self._components.items():
+            config["components"][name] = component.__class__.get_config_template(
+                description=description, env_var=env_var, exclude_fields=exclude_fields
+            )
+
+        with open(output_path / "container.yaml", "w") as f:
+            f.write(yaml.dump(config, sort_keys=False, allow_unicode=True))
+
+        return config
+
+    def from_config(self, config_data: dict | str | Path) -> None:
+        """Update container from configuration
+
+        Args:
+            config_data: The dict including connectors and components configurations
+        """
+        def clean_config_dict(config_dict: dict) -> dict:
+            """Recursively clean up the configuration dictionary, removing all 'description' and 'env_var' keys"""
+            cleaned = {}
+            for key, value in config_dict.items():
+                if isinstance(value, dict):
+                    if "value" in value:
+                        cleaned[key] = value["value"]
+                    else:
+                        cleaned[key] = clean_config_dict(value)
+                else:
+                    cleaned[key] = value
+            return cleaned
+
+        if isinstance(config_data, str | Path):
+            if not Path(config_data).exists():
+                if os.getenv("OMAGENT_MODE") == "lite":
+                    return 
+                else:
+                    raise FileNotFoundError(f"Config file not found: {config_data}")
+            config_data = yaml.load(open(config_data, "r"), Loader=yaml.FullLoader)
+        config_data = clean_config_dict(config_data)
+
+        if "conductor_config" in config_data:
+            self.conductor_config = Configuration(**config_data["conductor_config"])
+        if "aaas_config" in config_data:
+            self.aaas_config = AaasConfig(**config_data["aaas_config"])
+
+        # connectors
+        if "connectors" in config_data:
+            for name, config in config_data["connectors"].items():
+                connector_cls = registry.get_connector(config.pop("name"))
+                if connector_cls:
+                    self.register_connector(
+                        name=name, connector=connector_cls, overwrite=True, **config
+                    )
+
+        # components
+        if "components" in config_data:
+            for name, config in config_data["components"].items():
+                self.register_component(
+                    component=config.pop("name"),
+                    name=name,
+                    config=config,
+                    overwrite=True,
+                )
+
+        self.check_connection()
+
+    def check_connection(self):
+        if os.getenv("OMAGENT_MODE") == "lite":
+            return 
+        
+        for name, connector in self._connectors.items():
+            try:
+                connector.check_connection()
+            except Exception as e:
+                raise ConnectionError(
+                    f"Connection to {name} failed. Please check your connector config in container.yaml. \n Error Message: {e}"
+                )
+
+        try:
+            from omagent_core.engine.orkes.orkes_workflow_client import \
+                OrkesWorkflowClient
+
+            conductor_client = OrkesWorkflowClient(self.conductor_config)
+            conductor_client.check_connection()
+        except Exception as e:
+            raise ConnectionError(
+                f"Connection to Conductor failed. Please check your conductor config in container.yaml. \n Error Message: {e}"
+            )
+
+        print("--------------------------------")
+        print("All connections passed the connection check")
+        print("--------------------------------")
+
+
+container = Container()
diff --git a/omagent_core/utils/env.py b/omagent_core/utils/env.py
new file mode 100644
index 0000000000000000000000000000000000000000..212514ea6449e9440f7bf7c242df45275ed1c71b
--- /dev/null
+++ b/omagent_core/utils/env.py
@@ -0,0 +1,24 @@
+import os
+from distutils.util import strtobool
+
+
+class EnvVar(object):
+    APP_NAME = "OmAgent"
+
+    IS_DEBUG = strtobool(os.environ.get("IS_DEBUG", "false"))
+
+    STOP_AFTER_DELAY = int(
+        os.environ.get("STOP_AFTER_DELAY", 20)
+    )  # LLM will stop when the time from the first attempt >= limit
+    STOP_AFTER_ATTEMPT = int(os.environ.get("STOP_AFTER_ATTEMPT", 5))  # LLM retry times
+    LLM_CACHE_NUM = int(os.environ.get("LLM_CACHE_NUM", 500))  # LLM result cache number
+
+    MAX_NODE_RETRY = int(os.environ.get("MAX_NODE_RETRY", 3))
+
+    @classmethod
+    def update(cls, key, value):
+        setattr(cls, key, value)
+
+    @classmethod
+    def get(cls, key, default=None):
+        return getattr(cls, key, default)
diff --git a/omagent_core/utils/error.py b/omagent_core/utils/error.py
new file mode 100644
index 0000000000000000000000000000000000000000..1391f8c14ab9950be105230a7860eaa81a25fcc5
--- /dev/null
+++ b/omagent_core/utils/error.py
@@ -0,0 +1,39 @@
+class VQLError(Exception):
+    def __init__(self, code, msg=None, detail=""):
+        """
+        :param code: Error code
+        :param msg: Error message, for system display.
+        :param detail: Error detail, for debugging.
+        """
+
+        code2msg = {
+            500: "Internal Error",
+            501: "Image Error: Unable to Read",  # Unable to read image file
+            502: "Image Error: Corrupted Image",  # Image file is corrupted
+            503: "Image Error: Unable to Retrieve",  # Failed to retrieve image
+            504: "Image Error: Unrecognized Format",  # Unsupported image file format
+            505: "Image Error: Key Not Found",  # Key does not exist when retrieving data in Redis mode
+            506: "Image Error: Unable to Connect to Database",  # Unable to connect to Redis when retrieving data in Redis mode
+            511: "Request Error: Service Processing Failed",  # Atom error for unknown reasons
+            515: "Request Error: Max Retries Exceeded, Unable to Access Address",  # API URL or port error, unable to access
+            516: "Request Error: Invalid Address",  # Incorrect API route address
+            517: "Request Error: Invalid Request Format",  # Input parameter format error
+            518: "Request Error: Illegal Request Address",  # Incorrect format of atom API address
+            550: "Vector Database Error",
+            570: "Callback Error: Failed to Process Result Callback",  # Error occurred during algorithm result callback
+            800: "LLM Error: Unexpected Return Result",  # LLM return result does not meet expectations
+        }
+
+        self.code = code
+        self.detail = detail
+        if msg:
+            self.msg = msg
+        else:
+            self.msg = code2msg[code]
+
+    def __str__(self):
+        return repr(
+            "Code: {} | message: {} | detail:{}".format(
+                self.code, self.msg, self.detail
+            )
+        )
diff --git a/omagent_core/utils/general.py b/omagent_core/utils/general.py
new file mode 100644
index 0000000000000000000000000000000000000000..363b2cc8c53ca2cb1e0528b5943c919ee0c0c3e1
--- /dev/null
+++ b/omagent_core/utils/general.py
@@ -0,0 +1,152 @@
+import base64
+import os
+import platform
+from collections import OrderedDict
+from io import BytesIO
+from pathlib import Path
+from typing import Sequence
+
+import requests
+from PIL import Image
+
+
+class LRUCache:
+    # initializing capacity
+    def __init__(self, capacity: int):
+        self.cache = OrderedDict()
+        self.capacity = capacity
+
+    def has(self, key) -> bool:
+        return key in self.cache
+
+    # we return the value of the key
+    # that is queried in O(1) and return -1 if we
+    # don't find the key in out dict / cache.
+    # And also move the key to the end
+    # to show that it was recently used.
+    def get(self, key, default=None):
+        if key not in self.cache:
+            return default
+        else:
+            self.cache.move_to_end(key)
+            return self.cache[key]
+
+    # first, we add / update the key by conventional methods.
+    # And also move the key to the end to show that it was recently used.
+    # But here we will also check whether the length of our
+    # ordered dictionary has exceeded our capacity,
+    # If so we remove the first key (least recently used)
+    def put(self, key, value) -> None:
+        self.cache[key] = value
+        self.cache.move_to_end(key)
+        if len(self.cache) > self.capacity:
+            self.cache.popitem(last=False)
+
+    def pop(self, key, value):
+        self.cache.pop(key, None)
+
+
+def handle_response(res, json, url):
+    if res.status_code < 299:
+        return res.json()
+    elif res.status_code == 404:
+        logging.error("Request URL: {} | Error[404]: 请求错误: 错误的地址".format(url))
+        raise VQLError(516)
+    elif res.status_code == 422:
+        logging.error(
+            "Request URL: {} | Request body: {} | Error[422]: 请求错误: 错误的请求格式".format(
+                url, json
+            )
+        )
+        raise VQLError(517)
+    else:
+        info = res.json()
+        logging.error(
+            "Request URL: {} | Request body: {} | Error: {}".format(url, json, info)
+        )
+        raise VQLError(511, detail=info)
+
+
+def chunks(l: Sequence, win_len: int, stride_len: int):
+    s_id = 0
+    e_id = min(len(l), win_len)
+
+    while True:
+        yield l[s_id:e_id]
+
+        if e_id == len(l):
+            break
+
+        s_id = s_id + stride_len
+        e_id = min(s_id + win_len, len(l))
+
+
+def encode_image(input):
+    def _encode(img):
+        output_buffer = BytesIO()
+        img.save(output_buffer, format="JPEG")
+        byte_data = output_buffer.getvalue()
+        base64_str = base64.b64encode(byte_data)
+        base64_str = str(base64_str, encoding="utf-8")
+        return base64_str
+
+    input = input.convert("RGB")
+    if isinstance(input, list):
+        res = []
+        for img in input:
+            res.append(_encode(img))
+        return res
+    else:
+        return _encode(input)
+
+
+def get_platform() -> str:
+    """Get platform."""
+    system = platform.system()
+    if system == "Darwin":
+        return "MacOS"
+    return system
+
+
+def read_image(input_source) -> Image.Image:
+    """
+    Read an image from a local path, URL, PIL Image object, or Path object.
+
+    Args:
+        input_source (str or PIL.Image.Image or Path): The source of the image.
+            Can be a local file path, a URL, a PIL Image object, or a Path object.
+
+    Returns:
+        PIL.Image.Image: The image as a PIL Image object.
+
+    Raises:
+        ValueError: If the input source is invalid or the image cannot be read.
+    """
+    if isinstance(input_source, Image.Image):
+        return input_source
+
+    if isinstance(input_source, (str, Path)):
+        if isinstance(input_source, str) and input_source.startswith(
+            ("http://", "https://")
+        ):
+            # URL
+            try:
+                response = requests.get(input_source)
+                response.raise_for_status()
+                return Image.open(BytesIO(response.content))
+            except requests.RequestException as e:
+                raise ValueError(f"Failed to fetch image from URL: {e}")
+        elif os.path.isfile(str(input_source)):
+            # Local file path or Path object
+            try:
+                return Image.open(input_source)
+            except IOError as e:
+                raise ValueError(f"Failed to open local image file: {e}")
+        else:
+            raise ValueError(
+                "Invalid input source. Must be a valid URL or local file path."
+            )
+
+    raise ValueError(
+        "Invalid input type. Must be a string (URL or file path), Path object, or PIL Image object."
+    )
diff --git a/omagent_core/utils/handler.py b/omagent_core/utils/handler.py
new file mode 100644
index 0000000000000000000000000000000000000000..d82f4d43dd32c6f57cfd51432ea3d831f1165526
--- /dev/null
+++ b/omagent_core/utils/handler.py
@@ -0,0 +1,23 @@
+import logging
+
+import requests
+from omagent_core.engine.task_client import TaskClient
+
+
+class ConductorLogHandler(logging.Handler):
+    def __init__(self, task_client):
+        super().__init__()
+        self.task_client: TaskClient = task_client
+        self.task_id = None
+
+    def set_task_id(self, task_id):
+        self.task_id = task_id
+
+    def emit(self, record):
+        if not self.task_id:
+            return super().emit(record)
+        log_entry = self.format(record)
+        try:
+            self.task_client.log(log_entry, self.task_id)
+        except requests.exceptions.RequestException as e:
+            print(f"Failed to send log to Conductor: {e}")
diff --git a/omagent_core/utils/logger.py b/omagent_core/utils/logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..fc2c5fbf1e99012cd7d0294a5bc620cfea6951c7
--- /dev/null
+++ b/omagent_core/utils/logger.py
@@ -0,0 +1,71 @@
+import inspect
+import logging as _logging
+import logging.handlers as handlers
+import os
+from distutils.util import strtobool
+
+from omagent_core.utils.container import container
+
+
+class Logger(_logging.Logger):
+    def __init__(self):
+        pass
+
+    def init_logger(
+        self,
+        name,
+        log_name,
+        level=None,
+        roate="midnight",
+        backup_count=30,
+    ):
+        """
+        :param name: Name of the logger.
+        :param log_name: Name of the log file.
+        :param log_dir: The directory to save log files.
+        :param level: Log level: The lowest level of log.
+        :param Rotate: How to rotate log files.
+        :param backup_count: How many log files to keep.
+        """
+        if level is None:
+            level = (
+                _logging.DEBUG if container.conductor_config.debug else _logging.INFO
+            )
+        super().__init__(name, level)
+
+        formatter = _logging.Formatter(
+            "%(levelname)s | %(asctime)s | %(pathname)s:%(lineno)d | %(funcName)s | %(message)s"
+        )
+
+        if not self.handlers:
+            console_handler = _logging.StreamHandler()
+            self.addHandler(console_handler)
+            console_handler.setFormatter(formatter)
+
+    def _log_with_caller_info(self, level, msg, *args, **kwargs):
+        if self.isEnabledFor(level):
+            frame = inspect.currentframe()
+            outer_frames = inspect.getouterframes(frame)
+            if len(outer_frames) > 3:
+                calling_frame = outer_frames[3].frame
+                self.findCaller = lambda *args: (
+                    calling_frame.f_code.co_filename,
+                    calling_frame.f_lineno,
+                    calling_frame.f_code.co_name,
+                    None,
+                )
+            self._log(level, msg, args, **kwargs)
+            # Reset the findCaller to default after logging
+            self.findCaller = _logging.Logger.findCaller
+
+    def debug(self, msg, *args, **kwargs):
+        self._log_with_caller_info(_logging.DEBUG, msg, *args, **kwargs)
+
+    def info(self, msg, *args, **kwargs):
+        self._log_with_caller_info(_logging.INFO, msg, *args, **kwargs)
+
+    def error(self, msg, *args, **kwargs):
+        self._log_with_caller_info(_logging.ERROR, msg, *args, **kwargs)
+
+
+logging = Logger()
diff --git a/omagent_core/utils/plot.py b/omagent_core/utils/plot.py
new file mode 100644
index 0000000000000000000000000000000000000000..8066f099c323f36dc3b36af40a2134d7d75934df
--- /dev/null
+++ b/omagent_core/utils/plot.py
@@ -0,0 +1,56 @@
+from pathlib import Path
+
+from PIL import Image, ImageDraw, ImageFont
+
+
+class Annotator:
+    def __init__(self, im, line_width=None, font_size=None):
+        im = im if isinstance(im, Image.Image) else Image.fromarray(im)
+        self.im = im.copy()
+        self.draw = ImageDraw.Draw(self.im)
+        font = Path(__file__).parents[1].joinpath("resources/font.ttf")
+        self.font = ImageFont.truetype(
+            str(font), font_size or max(round(sum(self.im.size) / 2 * 0.035), 12)
+        )
+        self.lw = line_width or max(round(sum(im.size) / 2 * 0.003), 2)  # line width
+
+    def insure(self, y1, x2, fh):
+        x_bias = 0
+        y_bias = 0
+        if y1 < 0:
+            y_bias = fh
+        if x2 >= self.im.size[0]:
+            x_bias = x2 - self.im.size[0]
+        return x_bias, y_bias
+
+    def box_label(
+        self, box, label="", color=(128, 128, 128), txt_color=(255, 255, 255)
+    ):
+        # Add one xyxy box to image with label
+        self.draw.rectangle(box, width=self.lw, outline=color)  # box
+        if label:
+            fh = (self.font.getbbox(label)[3] - self.font.getbbox(label)[1]) + 3
+            w = self.font.getlength(label)  # text width
+            x_bias, y_bias = self.insure(box[1] - fh, box[0] + w + 1, fh)
+            self.draw.rectangle(
+                [
+                    box[0] - x_bias,
+                    box[1] + y_bias - fh,
+                    box[0] - x_bias + w + 1,
+                    box[1] + y_bias + 1,
+                ],
+                fill=color,
+            )
+            self.draw.text(
+                (box[0] - x_bias, box[1] + y_bias - 1),
+                label,
+                fill=txt_color,
+                font=self.font,
+                anchor="ls",
+            )
+
+    def polygon_label(self):
+        pass
+
+    def result(self):
+        return self.im
diff --git a/omagent_core/utils/registry.py b/omagent_core/utils/registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..12c0049be461f4eadeae51961290a92e61c7d73b
--- /dev/null
+++ b/omagent_core/utils/registry.py
@@ -0,0 +1,147 @@
+import importlib
+from functools import partial
+from pathlib import Path
+from typing import Any, Callable, Dict, List
+
+CATEGORIES = [
+    "prompt",
+    "llm",
+    "node",
+    "worker",
+    "tool",
+    "encoder",
+    "connector",
+    "component",
+]
+
+
+class Registry:
+    """Class for module registration and retrieval."""
+
+    def __init__(self):
+        # Initializes a mapping for different categories of modules.
+        self.mapping = {key: {} for key in CATEGORIES}
+
+    def __getattr__(self, name: str) -> Callable:
+        if name.startswith(("register_", "get_")):
+            prefix, category = name.split("_", 1)
+            if category in CATEGORIES:
+                if prefix == "register":
+                    return partial(self.register, category)
+                elif prefix == "get":
+                    return partial(self.get, category)
+        raise AttributeError(
+            f"'{self.__class__.__name__}' object has no attribute '{name}'"
+        )
+
+    def _register(self, category: str, name: str = None):
+        """
+        Registers a module under a specific category.
+
+        :param category: The category to register the module under.
+        :param name: The name to register the module as.
+        """
+
+        def wrap(module):
+            nonlocal name
+            name = name or module.__name__
+            if name in self.mapping[category]:
+                raise ValueError(
+                    f"Module {name} [{self.mapping[category].get(name)}] already registered in category {category}. Please use a different class name."
+                )
+            self.mapping.setdefault(category, {})[name] = module
+            return module
+
+        return wrap
+
+    def _get(self, category: str, name: str):
+        """
+        Retrieves a module from a specified category.
+
+        :param category: The category to search in.
+        :param name: The name of the module to retrieve.
+        :raises KeyError: If the module is not found.
+        """
+        try:
+            return self.mapping[category][name]
+        except KeyError:
+            raise KeyError(f"Module {name} not found in category {category}")
+
+    def register(self, category: str, name: str = None):
+        """
+        Registers a module under a general category.
+
+        :param category: The category to register the module under.
+        :param name: Optional name to register the module as.
+        """
+        return self._register(category, name)
+
+    def get(self, category: str, name: str):
+        """
+        Retrieves a module from a general category.
+
+        :param category: The category to search in.
+        :param name: The name of the module to retrieve.
+        """
+        return self._get(category, name)
+
+    def import_module(self, project_path: List[str] | str = None):
+        """Import modules from default paths and optional project paths.
+
+        Args:
+            project_path: Optional path or list of paths to import modules from
+        """
+        # Handle default paths
+        root_path = Path(__file__).parents[1]
+        default_path = [
+            root_path.joinpath("models"),
+            root_path.joinpath("tool_system"),
+            root_path.joinpath("services"),
+            root_path.joinpath("memories"),
+            root_path.joinpath("advanced_components"),
+            root_path.joinpath("clients"),
+        ]
+
+        for path in default_path:
+            for module in path.rglob("*.[ps][yo]"):
+                if module.name == "workflow.py":
+                    continue
+                module = str(module)
+                if "__init__" in module or "base.py" in module or "entry.py" in module:
+                    continue
+                module = "omagent_core" + module.rsplit("omagent_core", 1)[1].rsplit(
+                    ".", 1
+                )[0].replace("/", ".")
+                importlib.import_module(module)
+
+        # Handle project paths
+        if project_path:
+            if isinstance(project_path, (str, Path)):
+                project_path = [project_path]
+
+            for path in project_path:
+                path = Path(path).absolute()
+                project_root = path.parent
+                for module in path.rglob("*.[ps][yo]"):
+                    module = str(module)
+                    if "__init__" in module:
+                        continue
+                    module = (
+                        module.replace(str(project_root) + "/", "")
+                        .rsplit(".", 1)[0]
+                        .replace("/", ".")
+                    )
+                    importlib.import_module(module)
+
+
+# Instantiate registry
+registry = Registry()
+
+
+if __name__ == "__main__":
+
+    @registry.register_node()
+    class TestNode:
+        name: "TestNode"
+
+    print(registry.get_node("TestNode"))
diff --git a/omagent_core/utils/request.py b/omagent_core/utils/request.py
new file mode 100644
index 0000000000000000000000000000000000000000..09eaa9080aa5956eb31d46b38887876519d33d3d
--- /dev/null
+++ b/omagent_core/utils/request.py
@@ -0,0 +1,67 @@
+import httpx
+import requests
+
+from .error import VQLError
+from .logger import logging
+
+
+def request(
+    json: dict,
+    url: str,
+    headers: dict | None = None,
+) -> dict:
+    try:
+        res = requests.post(url=url, json=json, headers=headers)
+    except Exception as error:
+        logging.error(
+            "Request URL: {} | Request body: {} | Error: {}".format(url, json, error)
+        )
+        raise VQLError(511, detail=str(error))
+    if res.status_code < 299:
+        return res.json()
+    elif res.status_code == 404:
+        logging.error("Request URL: {} | Error[404]: 请求错误: 错误的地址".format(url))
+        raise VQLError(516)
+    elif res.status_code == 422:
+        logging.error(
+            "Request URL: {} | Request body: {} | Error[422]: 请求错误: 错误的请求格式".format(
+                url, json
+            )
+        )
+        raise VQLError(517)
+    else:
+        info = res.json()
+        logging.error(
+            "Request URL: {} | Request body: {} | Error: {}".format(url, json, info)
+        )
+        raise VQLError(511, detail=info)
+
+
+async def arequest(url: str, json: dict, headers: dict | None = None) -> dict:
+    try:
+        async with httpx.AsyncClient() as client:
+            res = await client.post(url=url, json=json, headers=headers, timeout=30)
+    except Exception as error:
+        logging.error(
+            "Request URL: {} | Request body: {} | Error: {}".format(url, json, error)
+        )
+        raise VQLError(511, detail=str(error))
+
+    if res.status_code < 299:
+        return res.json()
+    elif res.status_code == 404:
+        logging.error("Request URL: {} | Error[404]: 请求错误: 错误的地址".format(url))
+        raise VQLError(516)
+    elif res.status_code == 422:
+        logging.error(
+            "Request URL: {} | Request body: {} | Error[422]: 请求错误: 错误的请求格式".format(
+                url, json
+            )
+        )
+        raise VQLError(517)
+    else:
+        info = res.json()
+        logging.error(
+            "Request URL: {} | Request body: {} | Error: {}".format(url, json, info)
+        )
+        raise VQLError(511, detail=info)
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9a7841a9be09cdd03af657842308501ac725fdcd
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,120 @@
+aiofiles==23.2.1
+annotated-types==0.7.0
+anyio==4.8.0
+astor==0.8.1
+av==14.1.0
+beautifulsoup4==4.13.3
+bs4==0.0.2
+certifi==2024.12.14
+cffi==1.17.1
+charset-normalizer==3.4.1
+click==8.1.8
+colorama==0.4.6
+cryptography==44.0.1
+dacite==1.9.2
+decorator==5.1.1
+Deprecated @ file:///Users/cbousseau/work/recipes/ci_py311/deprecated_1677957593040/work
+distro==1.9.0
+dlib==19.24.6
+duckduckgo_search==6.4.2
+face-recognition==1.3.0
+face_recognition_models==0.3.0
+fakeredis==2.27.0
+fastapi==0.115.8
+ffmpy==0.5.0
+filelock==3.17.0
+fsspec==2025.2.0
+func_timeout==4.3.5
+future==1.0.0
+geocoder==1.38.1
+gmpy2 @ file:///private/var/folders/nz/j6p8yfhx1mv_0grj5xl4650h0000gp/T/abs_51391juwln/croot/gmpy2_1738085477864/work
+gradio==5.16.0
+gradio_client==1.7.0
+grpcio==1.67.1
+h11==0.14.0
+h2==4.2.0
+hpack==4.1.0
+httpcore==1.0.7
+httpx==0.28.1
+huggingface-hub==0.28.1
+hyperframe==6.1.0
+idna==3.10
+importlib-metadata @ file:///private/var/folders/k1/30mswbxs7r1g6zwn8y4fyt500000gp/T/abs_5498c88e7n/croot/importlib_metadata-suite_1704813534254/work
+Jinja2==3.1.5
+jiter==0.8.2
+json_repair==0.31.0
+jsonpath==0.82.2
+markdown-it-py==3.0.0
+MarkupSafe==2.1.5
+mdurl==0.1.2
+milvus-lite==2.4.11
+mpmath==1.3.0
+networkx==3.4.2
+numpy==2.2.3
+# Editable install with no version control (omagent_core==0.2.3)
+-e /Users/hanley/workspace/huggingface/test/omagent-core
+openai==1.63.0
+opencv-python==4.11.0.86
+opentelemetry-api @ file:///private/var/folders/k1/30mswbxs7r1g6zwn8y4fyt500000gp/T/abs_36ed_us4ue/croot/opentelemetry-api_1724767678005/work
+orjson==3.10.15
+packaging==24.2
+pandas==2.2.3
+pillow==11.1.0
+platformdirs==4.3.6
+primp==0.12.1
+prometheus_client==0.21.1
+protobuf==5.29.3
+psutil==7.0.0
+pycparser==2.22
+pycryptodome==3.21.0
+pydantic==2.10.6
+pydantic-settings==2.7.1
+pydantic_core==2.27.2
+pydub==0.25.1
+Pygments==2.19.1
+pymilvus==2.5.4
+PyMySQL==1.1.1
+python-dateutil==2.9.0.post0
+python-dotenv==1.0.1
+python-multipart==0.0.20
+pytz==2025.1
+PyYAML==6.0.2
+qwen-vl-utils==0.0.8
+ratelim==0.1.6
+redis==5.2.1
+redislite==6.2.912183
+regex==2024.11.6
+requests==2.32.3
+rich==13.9.4
+ruff==0.9.6
+safehttpx==0.1.6
+scenedetect==0.6.5.2
+semantic-version==2.10.0
+shellingham==1.5.4
+shortuuid==1.0.13
+six==1.17.0
+sniffio==1.3.1
+sortedcontainers==2.4.0
+soupsieve==2.6
+SQLAlchemy==2.0.38
+SQLAlchemy-Utils==0.41.2
+sqlmodel==0.0.22
+starlette==0.45.3
+sympy==1.13.3
+tavily-python==0.5.1
+tenacity==9.0.0
+tiktoken==0.9.0
+tomlkit==0.13.2
+torch==2.6.0
+torchvision==0.21.0
+tqdm==4.67.1
+typer==0.15.1
+typing_extensions==4.12.2
+tzdata==2025.1
+ujson==5.10.0
+urllib3==2.3.0
+uvicorn==0.34.0
+websockets==14.2
+wikipedia==1.4.0
+wrapt @ file:///private/var/folders/nz/j6p8yfhx1mv_0grj5xl4650h0000gp/T/abs_30cvf__dq_/croot/wrapt_1736541623359/work
+zipp @ file:///private/var/folders/k1/30mswbxs7r1g6zwn8y4fyt500000gp/T/abs_echurpkwug/croot/zipp_1732630743967/work
diff --git a/run_cli.py b/run_cli.py
new file mode 100644
index 0000000000000000000000000000000000000000..d64db2bc54d1b993e6a2ef487c103ad0994e679c
--- /dev/null
+++ b/run_cli.py
@@ -0,0 +1,79 @@
+# Import required modules and components
+import os
+os.environ["OMAGENT_MODE"] = "lite"
+from pathlib import Path
+
+from agent.conclude.conclude import Conclude
+from agent.video_preprocessor.video_preprocess import VideoPreprocessor
+from agent.video_qa.qa import VideoQA
+from omagent_core.advanced_components.workflow.dnc.workflow import DnCWorkflow
+from omagent_core.clients.devices.cli import DefaultClient
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.engine.workflow.task.do_while_task import (DnCLoopTask,
+                                                             InfiniteLoopTask)
+from omagent_core.engine.workflow.task.set_variable_task import SetVariableTask
+from omagent_core.engine.workflow.task.simple_task import simple_task
+from omagent_core.engine.workflow.task.switch_task import SwitchTask
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+logging.init_logger("omagent", "omagent", level="INFO")
+
+# Set current working directory path
+CURRENT_PATH = root_path = Path(__file__).parents[0]
+
+# Import registered modules
+registry.import_module(project_path=CURRENT_PATH.joinpath("agent"))
+
+# Load container configuration from YAML file
+container.register_stm("SharedMemSTM")
+container.register_ltm(ltm="VideoMilvusLTM")
+container.from_config(CURRENT_PATH.joinpath("container.yaml"))
+
+# Initialize simple VQA workflow
+workflow = ConductorWorkflow(name="video_understanding")
+
+# 1. Video preprocess task for video preprocessing
+video_preprocess_task = simple_task(
+    task_def_name=VideoPreprocessor, task_reference_name="video_preprocess"
+)
+
+# 2. Video QA task for video QA
+video_qa_task = simple_task(
+    task_def_name=VideoQA,
+    task_reference_name="video_qa",
+    inputs={
+        "video_md5": video_preprocess_task.output("video_md5"),
+        "video_path": video_preprocess_task.output("video_path"),
+        "instance_id": video_preprocess_task.output("instance_id"),
+    },
+)
+
+dnc_workflow = DnCWorkflow()
+dnc_workflow.set_input(query=video_qa_task.output("query"))
+
+# 7. Conclude task for task conclusion
+conclude_task = simple_task(
+    task_def_name=Conclude,
+    task_reference_name="task_conclude",
+    inputs={
+        "dnc_structure": dnc_workflow.dnc_structure,
+        "last_output": dnc_workflow.last_output,
+    },
+)
+
+
+# Configure workflow execution flow: Input -> Initialize global variables -> DnC Loop -> Conclude
+workflow >> video_preprocess_task >> video_qa_task >> dnc_workflow >> conclude_task
+
+# Register workflow
+workflow.register(overwrite=True)
+
+# Initialize and start app client with workflow configuration
+cli_client = DefaultClient(
+    interactor=workflow, config_path="configs"
+)
+cli_client.start_interactor()
diff --git a/tool/face_rec/face_rec.py b/tool/face_rec/face_rec.py
new file mode 100755
index 0000000000000000000000000000000000000000..506dc532b962e953932ad8da0c2625ac0f04de93
--- /dev/null
+++ b/tool/face_rec/face_rec.py
@@ -0,0 +1,104 @@
+import pickle
+from pathlib import Path
+from typing import List, Optional
+
+import cv2
+import face_recognition
+import numpy as np
+from PIL import Image
+from pydantic import model_validator
+
+from ....models.od.schemas import Target
+from ....utils.registry import registry
+from ...base import ArgSchema, BaseModelTool
+
+ARGSCHEMA = {}
+
+
+@registry.register_tool()
+class FaceRecognition(BaseModelTool):
+    args_schema: ArgSchema = ArgSchema(**ARGSCHEMA)
+    description: str = (
+        "This tool can recognize facial information in images/extracted frames and identify who the person is."
+        "Images/extracted frames are already loaded."
+    )
+    threshold: float = 0.6
+    num_jitters: int = 1
+    face_db: str = "data/face_db"
+    model: str = "large"
+    loaded_face_db: Optional[dict] = None
+
+    @model_validator(mode="after")
+    def face_db_validator(self) -> "FaceRecognition":
+        if self.loaded_face_db is None:
+            if Path(self.face_db).exists():
+                self.loaded_face_db = self._load_face_db(self.face_db)
+            else:
+                raise ValueError(f"Face database not found at {self.face_db}")
+        elif isinstance(self.loaded_face_db, dict):
+            if (
+                "embeddings" not in self.loaded_face_db
+                or "names" not in self.loaded_face_db
+            ):
+                raise ValueError(
+                    "Face database must have 'embeddings' and 'names' keys."
+                )
+        else:
+            raise ValueError("Face database must be a dictionary.")
+        return self
+
+    def _load_face_db(self, path: str):
+        cached_model = Path(path).joinpath(f"representations_{self.model}_face.pkl")
+        # if cached_model.exists():
+        #     loaded_face_db = pickle.load(open(cached_model, "rb"))
+        # else:
+        face_db = Path(path)
+        embeddings = []
+        names = []
+        for known_image in face_db.rglob("*"):
+            if known_image.suffix in [".jpg", ".png", ".webp"]:
+                loaded_image = np.array(Image.open(known_image).convert("RGB"))
+                loaded_image = cv2.cvtColor(loaded_image, cv2.COLOR_RGB2BGR)
+                known_encoding = face_recognition.face_encodings(
+                    loaded_image, model="large"
+                )[0]
+                embeddings.append(known_encoding)
+                names.append(known_image.parent.name)
+        loaded_face_db = {"embeddings": embeddings, "names": names}
+        pickle.dump(loaded_face_db, open(cached_model, "wb"))
+        return loaded_face_db
+
+    def infer(self, image: Image.Image) -> List[Target]:
+        img = np.array(image.convert("RGB"))
+        img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
+        face_locations = face_recognition.face_locations(img)
+        face_encodings = face_recognition.face_encodings(img, face_locations)
+        rec_res = []
+        for (top, right, bottom, left), face_encoding in zip(
+            face_locations, face_encodings
+        ):
+            face_distances = face_recognition.face_distance(
+                self.loaded_face_db.get("embeddings"), face_encoding
+            )
+            best_match_index = np.argmin(face_distances)
+            if face_distances[best_match_index] <= self.threshold:
+                name = self.loaded_face_db["names"][best_match_index]
+                bbox = [left, top, right, bottom]
+                rec_res.append(
+                    Target(label=name, bbox=bbox, conf=face_distances[best_match_index])
+                )
+        return rec_res
+
+    def _run(self):
+        names = set()
+        for key in self.stm.image_cache.keys():
+            anno = self.infer(self.stm.image_cache[key])
+            self.stm.image_cache[key] = self.visual_prompting(
+                self.stm.image_cache[key], anno
+            )
+            names.update([item.label for item in anno])
+
+        return f"Recognized {len(names)} faces: {', '.join(names)}"
+
+    async def _arun(self):
+        return self._run()
diff --git a/webpage.py b/webpage.py
new file mode 100644
index 0000000000000000000000000000000000000000..59723c6835c3bf37e4ea0d28213869f2e23cad55
--- /dev/null
+++ b/webpage.py
@@ -0,0 +1,507 @@
+import html
+import json
+import os
+import queue
+import shutil
+import sys
+import threading
+import uuid
+from pathlib import Path
+from time import sleep
+
+os.environ['GRADIO_TEMP_DIR'] = os.getcwd()
+video_root_path = os.path.join(os.getcwd(), 'video_root')
+os.makedirs(video_root_path, exist_ok=True)
+
+from omagent_core.clients.devices.app.callback import AppCallback
+from omagent_core.clients.devices.app.input import AppInput
+from omagent_core.clients.devices.app.schemas import ContentStatus, MessageType
+from omagent_core.engine.automator.task_handler import TaskHandler
+from omagent_core.engine.http.models.workflow_status import terminal_status
+from omagent_core.engine.workflow.conductor_workflow import ConductorWorkflow
+from omagent_core.services.connectors.redis import RedisConnector
+from omagent_core.utils.build import build_from_file
+from omagent_core.utils.container import container
+from omagent_core.utils.logger import logging
+from omagent_core.utils.registry import registry
+
+registry.import_module()
+
+container.register_connector(name="redis_stream_client", connector=RedisConnector)
+# container.register_stm(stm='RedisSTM')
+container.register_callback(callback=AppCallback)
+container.register_input(input=AppInput)
+
+import gradio as gr
+
+
+class WebpageClient:
+    def __init__(
+            self,
+            interactor: ConductorWorkflow = None,
+            processor: ConductorWorkflow = None,
+            config_path: str = "./config",
+            workers: list = [],
+    ) -> None:
+        self._interactor = interactor
+        self._processor = processor
+        self._config_path = config_path
+        self._workers = workers
+        self._workflow_instance_id = None
+        self._worker_config = build_from_file(self._config_path)
+        self._task_to_domain = {}
+        self._incomplete_message = ""
+        self._custom_css = """
+            #OmAgent {
+                height: 100vh !important;
+                max-height: calc(100vh - 190px) !important;
+                overflow-y: auto;
+            }
+
+            .running-message {
+                margin: 0;
+                padding: 2px 4px;
+                white-space: pre-wrap;
+                word-wrap: break-word;
+                font-family: inherit;
+            }
+
+            /* Remove the background and border of the message box */
+            .message-wrap {
+                background: none !important;
+                border: none !important;
+                padding: 0 !important;
+                margin: 0 !important;
+            }
+
+            /* Remove the bubble style of the running message */
+            .message:has(.running-message) {
+                background: none !important;
+                border: none !important;
+                padding: 0 !important;
+                box-shadow: none !important;
+            }
+        """
+        self.workflow_instance_id = str(uuid.uuid4())
+        self.processor_instance_id = str(uuid.uuid4())
+        worker_config = build_from_file(self._config_path)
+        self.initialization(workers, worker_config)
+    
+    def initialization(self, workers, worker_config):
+        self.workers = {}
+        for worker in workers:
+            worker.workflow_instance_id = self.workflow_instance_id
+            self.workers[type(worker).__name__] = worker
+        
+        for config in worker_config:
+            worker_cls = registry.get_worker(config['name'])
+            worker = worker_cls(**config)
+            worker.workflow_instance_id = self.workflow_instance_id
+            self.workers[config['name']] = worker
+        
+    def gradio_app(self):
+        
+        with gr.Blocks() as demo:
+
+            def load_local_video() -> dict:
+                result = {}
+                for root, _, files in os.walk(video_root_path):
+                    for file in filter(lambda x: x.split('.')[-1] in (
+                            'mp4', 'avi', 'mov', 'wmv', 'flv', 'mkv', 'webm', 'm4v'), files):
+                        file_obs_path = os.path.join(root, file)
+                        result[Path(file_obs_path).name] = file_obs_path
+                return result
+            
+            video_dict = load_local_video()
+            current_video = None
+            state = gr.State(value={
+                'video_dict': video_dict,
+                'current_video': current_video
+            })
+            with gr.Row():
+                with gr.Column():
+                    with gr.Column():
+                        def display_video_map(video_title):
+                            # change display video
+                            video_path = state.value.get('video_dict', {}).get(video_title)
+                            
+                            exception_queue = queue.Queue()
+                            workflow_input = {'video_path': video_path}
+                            processor_result = None
+                            
+                            def run_workflow(workflow_input):
+                                nonlocal processor_result
+                                try:
+                                    processor_result = self._processor.start_workflow_with_input(
+                                        workflow_input=workflow_input, workers=self.workers
+                                    )
+                                except Exception as e:
+                                    exception_queue.put(e)  # add exception to queue
+                                    logging.error(f"Error starting workflow: {e}")
+                                    raise e
+                            
+                            workflow_thread = threading.Thread(target=run_workflow, args=(workflow_input,), daemon=True)
+                            workflow_thread.start()
+                            
+                            processor_workflow_instance_id = self.processor_instance_id
+                            while True:
+                                status = self._processor.get_workflow(
+                                    workflow_id=processor_workflow_instance_id).status
+                                if status in terminal_status:
+                                    break
+                                sleep(1)
+                            
+                            state.value['video_dict'] = load_local_video()
+                            state.value.update(current_video=video_path)
+                            state.value.update(processor_result=processor_result)
+                            state.value.update(processor_workflow_instance_id=processor_workflow_instance_id)
+                        
+                            return video_path, state
+                            
+                        select_video = gr.Dropdown(
+                            state.value['video_dict'].keys(),
+                            value=None
+                        )
+                        display_video = gr.Video(
+                            state.value['current_video'],
+                        )
+                        select_video.change(
+                            fn=display_video_map,
+                            inputs=[select_video],
+                            outputs=[display_video, state]
+                        )
+                
+                with gr.Column():
+                    chatbot = gr.Chatbot(
+                        type="messages",
+                    )
+                    
+                    chat_input = gr.Textbox(
+                        interactive=True,
+                        placeholder="Enter message...",
+                        show_label=False,
+                    )
+                    
+                    chat_msg = chat_input.submit(
+                        self.add_message,
+                        [chatbot, chat_input, state],
+                        [chatbot, chat_input]
+                    )
+                    bot_msg = chat_msg.then(
+                        self.bot, (chatbot, state), chatbot, api_name="bot_response"
+                    )
+                    bot_msg.then(
+                        lambda: gr.Textbox(interactive=True), None, [chat_input]
+                    )
+            
+            demo.launch(
+                max_file_size='1gb'
+            )
+        
+    def start_interactor(self):
+
+        try:
+            self.gradio_app()
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if self._workflow_instance_id is not None:
+                self._interactor._executor.terminate(
+                    workflow_id=self._workflow_instance_id
+                )
+            raise
+    
+    def stop_interactor(self):
+        # self._task_handler_interactor.stop_processes()
+        print("stop_interactor")
+        sys.exit(0)
+        
+    def start_processor(self):
+        self._task_handler_processor = TaskHandler(
+            worker_config=self._worker_config, workers=self._workers, task_to_domain=self._task_to_domain
+        )
+        self._task_handler_processor.start_processes()
+        
+        try:
+            with gr.Blocks(title="OmAgent", css=self._custom_css) as chat_interface:
+                chatbot = gr.Chatbot(
+                    elem_id="OmAgent",
+                    bubble_full_width=False,
+                    type="messages",
+                    height="100%",
+                )
+                
+                chat_input = gr.MultimodalTextbox(
+                    interactive=True,
+                    file_count="multiple",
+                    placeholder="Enter message or upload file...",
+                    show_label=False,
+                )
+                
+                chat_msg = chat_input.submit(
+                    self.add_processor_message,
+                    [chatbot, chat_input],
+                    [chatbot, chat_input],
+                )
+                bot_msg = chat_msg.then(
+                    self.processor_bot, chatbot, chatbot, api_name="bot_response"
+                )
+                bot_msg.then(
+                    lambda: gr.MultimodalTextbox(interactive=True), None, [chat_input]
+                )
+            chat_interface.launch(server_port=7861)
+        except KeyboardInterrupt:
+            logging.info("\nDetected Ctrl+C, stopping workflow...")
+            if self._workflow_instance_id is not None:
+                self._processor._executor.terminate(
+                    workflow_id=self._workflow_instance_id
+                )
+            raise
+    
+    def stop_processor(self):
+        self._task_handler_processor.stop_processes()
+    
+    def add_message(self, history, message, state):
+        if isinstance(state, gr.State):
+            if state.value.get('current_video') is None:
+                history.append({"role": "user", "content": message})
+                history.append({"role": "assistant", "content": 'Please select a video'})
+                return history, gr.Textbox(value=None, interactive=False)
+        else:
+            if state.get('current_video') is None:
+                history.append({"role": "user", "content": message})
+                history.append({"role": "assistant", "content": 'Please select a video'})
+                return history, gr.Textbox(value=None, interactive=False)
+        if self._workflow_instance_id is None:
+            workflow_input = {
+                'question': message,
+                "video_md5": state.value.get('processor_result', {}).get("video_md5"),
+                "video_path": state.value.get('processor_result', {}).get("video_path"),
+                "instance_id": state.value.get('processor_result', {}).get("instance_id"),
+                "processor_workflow_instance_id": state.value.get("processor_workflow_instance_id")
+            }
+            exception_queue = queue.Queue()
+            
+            def run_workflow(workflow_input):
+                try:
+                    self._interactor.start_workflow_with_input(
+                        workflow_input=workflow_input, workers=self.workers
+                    )
+                except Exception as e:
+                    exception_queue.put(e)  # add exception to queue
+                    logging.error(f"Error starting workflow: {e}")
+                    raise e
+            
+            workflow_thread = threading.Thread(target=run_workflow, args=(workflow_input,),daemon=True)
+            workflow_thread.start()
+
+            self._workflow_instance_id = self.workflow_instance_id
+        contents = []
+        history.append({"role": "user", "content": message})
+        contents.append({"data": message, "type": "text"})
+        result = {
+            "agent_id": self._workflow_instance_id,
+            "messages": [{"role": "user", "content": contents}],
+            "kwargs": {},
+        }
+        container.get_connector("redis_stream_client")._client.xadd(
+            f"{self._workflow_instance_id}_input",
+            {"payload": json.dumps(result, ensure_ascii=False)},
+        )
+        return history, gr.Textbox(value=None, interactive=False)
+    
+    def add_processor_message(self, history, message):
+        if self._workflow_instance_id is None:
+            self._workflow_instance_id = self._processor.start_workflow_with_input(
+                workflow_input={}, task_to_domain=self._task_to_domain
+            )
+        image_items = []
+        for idx, x in enumerate(message["files"]):
+            history.append({"role": "user", "content": {"path": x}})
+            image_items.append(
+                {"type": "image_url", "resource_id": str(idx), "data": str(x)}
+            )
+        result = {"content": image_items}
+        container.get_connector("redis_stream_client")._client.xadd(
+            f"image_process", {"payload": json.dumps(result, ensure_ascii=False)}
+        )
+        return history, gr.MultimodalTextbox(value=None, interactive=False)
+    
+    def bot(self, history, state):
+        if isinstance(state, gr.State):
+            if state.value.get('current_video') is None:
+                yield history
+                return
+        else:
+            if state.get('current_video') is None:
+                yield history
+                return
+            
+        stream_name = f"{self._workflow_instance_id}_output"
+        consumer_name = f"{self._workflow_instance_id}_agent"  # consumer name
+        group_name = "omappagent"  # replace with your consumer group name
+        running_stream_name = f"{self._workflow_instance_id}_running"
+        self._check_redis_stream_exist(stream_name, group_name)
+        self._check_redis_stream_exist(running_stream_name, group_name)
+        while True:
+            # read running stream
+            running_messages = self._get_redis_stream_message(
+                group_name, consumer_name, running_stream_name
+            )
+            for stream, message_list in running_messages:
+                for message_id, message in message_list:
+                    payload_data = self._get_message_payload(message)
+                    if payload_data is None:
+                        continue
+                    progress = html.escape(payload_data.get("progress", ""))
+                    message = html.escape(payload_data.get("message", ""))
+                    formatted_message = (
+                        f'<pre class="running-message">{progress}: {message}</pre>'
+                    )
+                    history.append({"role": "assistant", "content": formatted_message})
+                    yield history
+                    
+                    container.get_connector("redis_stream_client")._client.xack(
+                        running_stream_name, group_name, message_id
+                    )
+            # read output stream
+            messages = self._get_redis_stream_message(
+                group_name, consumer_name, stream_name
+            )
+            finish_flag = False
+            
+            for stream, message_list in messages:
+                for message_id, message in message_list:
+                    incomplete_flag = False
+                    payload_data = self._get_message_payload(message)
+                    if payload_data is None:
+                        continue
+                    if payload_data["content_status"] == ContentStatus.INCOMPLETE.value:
+                        incomplete_flag = True
+                    message_item = payload_data["message"]
+                    if message_item["type"] == MessageType.IMAGE_URL.value:
+                        history.append(
+                            {
+                                "role": "assistant",
+                                "content": {"path": message_item["content"]},
+                            }
+                        )
+                    else:
+                        if incomplete_flag:
+                            self._incomplete_message = (
+                                    self._incomplete_message + message_item["content"]
+                            )
+                            if history and history[-1]["role"] == "assistant":
+                                history[-1]["content"] = self._incomplete_message
+                            else:
+                                history.append(
+                                    {
+                                        "role": "assistant",
+                                        "content": self._incomplete_message,
+                                    }
+                                )
+                        else:
+                            if self._incomplete_message != "":
+                                self._incomplete_message = (
+                                        self._incomplete_message + message_item["content"]
+                                )
+                                if history and history[-1]["role"] == "assistant":
+                                    history[-1]["content"] = self._incomplete_message
+                                else:
+                                    history.append(
+                                        {
+                                            "role": "assistant",
+                                            "content": self._incomplete_message,
+                                        }
+                                    )
+                                self._incomplete_message = ""
+                            else:
+                                history.append(
+                                    {
+                                        "role": "assistant",
+                                        "content": message_item["content"],
+                                    }
+                                )
+                    
+                    yield history
+                    
+                    container.get_connector("redis_stream_client")._client.xack(
+                        stream_name, group_name, message_id
+                    )
+                    
+                    # check finish flag
+                    if (
+                            "interaction_type" in payload_data
+                            and payload_data["interaction_type"] == 1
+                    ):
+                        finish_flag = True
+                    if (
+                            "content_status" in payload_data
+                            and payload_data["content_status"]
+                            == ContentStatus.END_ANSWER.value
+                    ):
+                        self._workflow_instance_id = None
+                        finish_flag = True
+            
+            if finish_flag:
+                break
+            sleep(0.01)
+    
+    def processor_bot(self, history: list):
+        history.append({"role": "assistant", "content": f"processing..."})
+        yield history
+        while True:
+            status = self._processor.get_workflow(
+                workflow_id=self._workflow_instance_id
+            ).status
+            if status in terminal_status:
+                history.append({"role": "assistant", "content": f"completed"})
+                yield history
+                self._workflow_instance_id = None
+                break
+            sleep(0.01)
+    
+    def _get_redis_stream_message(
+            self, group_name: str, consumer_name: str, stream_name: str
+    ):
+        messages = container.get_connector("redis_stream_client")._client.xreadgroup(
+            group_name, consumer_name, {stream_name: ">"}, count=1
+        )
+        messages = [
+            (
+                stream,
+                [
+                    (
+                        message_id,
+                        {
+                            k.decode("utf-8"): v.decode("utf-8")
+                            for k, v in message.items()
+                        },
+                    )
+                    for message_id, message in message_list
+                ],
+            )
+            for stream, message_list in messages
+        ]
+        return messages
+    
+    def _check_redis_stream_exist(self, stream_name: str, group_name: str):
+        try:
+            container.get_connector("redis_stream_client")._client.xgroup_create(
+                stream_name, group_name, id="0", mkstream=True
+            )
+        except Exception as e:
+            logging.debug(f"Consumer group may already exist: {e}")
+    
+    def _get_message_payload(self, message: dict):
+        logging.info(f"Received running message: {message}")
+        payload = message.get("payload")
+        # check payload data
+        if not payload:
+            logging.error("Payload is empty")
+            return None
+        try:
+            payload_data = json.loads(payload)
+        except json.JSONDecodeError as e:
+            logging.error(f"Payload is not a valid JSON: {e}")
+            return None
+        return payload_data
diff --git a/webpage_configs/llms/gpt4o.yml b/webpage_configs/llms/gpt4o.yml
new file mode 100755
index 0000000000000000000000000000000000000000..0d612dfcf04a5b9b4d7bba94b01b495d71df413e
--- /dev/null
+++ b/webpage_configs/llms/gpt4o.yml
@@ -0,0 +1,7 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+vision: true
+response_format: json_object
\ No newline at end of file
diff --git a/webpage_configs/llms/json_res.yml b/webpage_configs/llms/json_res.yml
new file mode 100755
index 0000000000000000000000000000000000000000..c5cf8adbfcd184c269548df686c269394e29ec70
--- /dev/null
+++ b/webpage_configs/llms/json_res.yml
@@ -0,0 +1,6 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+response_format: json_object
\ No newline at end of file
diff --git a/webpage_configs/llms/text_encoder.yml b/webpage_configs/llms/text_encoder.yml
new file mode 100644
index 0000000000000000000000000000000000000000..4df3f875fd1fe27659f4da6633c9b78025514049
--- /dev/null
+++ b/webpage_configs/llms/text_encoder.yml
@@ -0,0 +1,3 @@
+name: OpenaiTextEmbeddingV3
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+api_key: ${env| custom_openai_key, openai_api_key}
\ No newline at end of file
diff --git a/webpage_configs/llms/text_res.yml b/webpage_configs/llms/text_res.yml
new file mode 100755
index 0000000000000000000000000000000000000000..b6c80bdfa8e89d7c14d6866aed7614c18459b64d
--- /dev/null
+++ b/webpage_configs/llms/text_res.yml
@@ -0,0 +1,6 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+response_format: text
\ No newline at end of file
diff --git a/webpage_configs/llms/text_res_stream.yml b/webpage_configs/llms/text_res_stream.yml
new file mode 100755
index 0000000000000000000000000000000000000000..e7dedc25b92798eddf3d42cd9aa7ed37d39addbb
--- /dev/null
+++ b/webpage_configs/llms/text_res_stream.yml
@@ -0,0 +1,7 @@
+name: OpenaiGPTLLM
+model_id: gpt-4o-mini
+api_key: ${env| custom_openai_key, openai_api_key}
+endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+temperature: 0
+stream: true
+response_format: text
\ No newline at end of file
diff --git a/webpage_configs/tools/all_tools.yml b/webpage_configs/tools/all_tools.yml
new file mode 100755
index 0000000000000000000000000000000000000000..801297af0dc60cd9c97ab498399eb8d18e5e2f0a
--- /dev/null
+++ b/webpage_configs/tools/all_tools.yml
@@ -0,0 +1,12 @@
+llm: ${sub|text_res}
+tools:
+    - Calculator
+    - CodeInterpreter
+    - ReadFileContent
+    - WriteFileContent
+    - ShellTool
+    - name: Rewinder
+      llm: ${sub|text_res}
+    - name: WebSearch
+      bing_api_key: ${env|bing_api_key, microsoft_bing_api_key}
+      llm: ${sub|text_res}
diff --git a/webpage_configs/workers/web_conclude.yml b/webpage_configs/workers/web_conclude.yml
new file mode 100755
index 0000000000000000000000000000000000000000..3842b6900594c99f1b8fb8853dba85f76dfbf242
--- /dev/null
+++ b/webpage_configs/workers/web_conclude.yml
@@ -0,0 +1,4 @@
+name: WebpageConclude
+llm: ${sub|text_res_stream}
+output_parser: 
+  name: StrParser
\ No newline at end of file
diff --git a/webpage_configs/workers/web_dnc_workflow.yml b/webpage_configs/workers/web_dnc_workflow.yml
new file mode 100755
index 0000000000000000000000000000000000000000..9d248e4037ab95a1f8a7c23bf6bb155649f34906
--- /dev/null
+++ b/webpage_configs/workers/web_dnc_workflow.yml
@@ -0,0 +1,18 @@
+- name: ConstructDncPayload
+- name: StructureUpdate
+- name: TaskConqueror
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskDivider
+  llm: ${sub|json_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskRescue
+  llm: ${sub|text_res}
+  tool_manager: ${sub|all_tools}
+  output_parser: 
+    name: StrParser
+- name: TaskExitMonitor
diff --git a/webpage_configs/workers/web_video_preprocessor.yml b/webpage_configs/workers/web_video_preprocessor.yml
new file mode 100755
index 0000000000000000000000000000000000000000..186a23cc14b987eece2aaf8b2991fec5eaf7e9d5
--- /dev/null
+++ b/webpage_configs/workers/web_video_preprocessor.yml
@@ -0,0 +1,12 @@
+name: WebpageVideoPreprocessor
+llm: ${sub|gpt4o}
+use_cache: true
+scene_detect_threshold: 27
+frame_extraction_interval: 5
+stt:
+  name: STT
+  endpoint: ${env| custom_openai_endpoint, https://api.openai.com/v1}
+  api_key: ${env| custom_openai_key, openai_api_key}
+output_parser:
+  name: DictParser
+text_encoder: ${sub| text_encoder}
\ No newline at end of file
diff --git a/webpage_configs/workers/web_video_qa.yml b/webpage_configs/workers/web_video_qa.yml
new file mode 100755
index 0000000000000000000000000000000000000000..3fecfb7630780fff7c9e4527224385ce6880d1dd
--- /dev/null
+++ b/webpage_configs/workers/web_video_qa.yml
@@ -0,0 +1,5 @@
+name: WebpageVideoQA
+llm: ${sub|gpt4o}
+output_parser: 
+  name: StrParser
+text_encoder: ${sub| text_encoder}
\ No newline at end of file