diff --git a/README.md b/README.md index 1fd7235..4c870f2 100644 --- a/README.md +++ b/README.md @@ -139,9 +139,56 @@ docker run \ "ghcr.io/githubsecuritylab/seclab-taskflow-agent" "$@" ``` +## General YAML file headers + +Every YAML files used by the Seclab Taskflow Agent must include a header like this: + +```yaml +seclab-taskflow-agent: + version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/CVE-2023-2283/CVE-2023-2283 +``` + +The `filetype` determines whether the file defines a personality, toolbox, or +taskflow. This means that different types of files can be stored in the same directory. +A `filetype` can be one of the followings: + - taskflow + - personality + - toolbox + - prompt + - model_config + +We'll explain these file types in more detail in the following sections. + +The `filekey` is a unique name for the file. It is used to allow +cross-referencing between files. For example, a taskflow can reference +a personality by its filekey. Because filekeys are used for +cross-referencing (rather than file paths), it means that you can move +a file to a different directory without breaking the links. This also +means that you can easily import new files by dropping them into a sub-directory. +We recommend including something like your +GitHub `/` in your filekeys to make them globally unique. + +In the above example, it is a `taskflow` file with `filekey` `GitHubSecurityLab/seclab-taskflow-agent/taskflows/CVE-2023-2283/CVE-2023-2283`. The `filekey` is needed to run the taskflow from command line, e.g.: + +``` +python3 main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/CVE-2023-2283/CVE-2023-2283 +``` + +will run the taskflow. + +The `version` number in the header should always be 1. It means that the +file uses version 1 of the seclab-taskflow-agent syntax. If we ever need +to make a major change to the syntax, then we'll update the version number. +This will hopefully enable us to make changes without breaking backwards +compatibility. + +We'll now explain the role of different types of files and functionalities available to them. + ## Personalities -Core characteristics for a single Agent. Configured through YAML files in `personalities/`. +Core characteristics for a single Agent. Configured through YAML files of `filetype` `personality`. These are system prompt level instructions. @@ -152,7 +199,7 @@ Example: seclab-taskflow-agent: version: 1 filetype: personality - filekey: personalities/examples/echo + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo personality: | You are a simple echo bot. You use echo tools to echo things. @@ -162,14 +209,60 @@ task: | # personality toolboxes map to mcp servers made available to this Agent toolboxes: - - toolboxes/echo + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/echo ``` +In the above, the `personality` and `task` field specifies the system prompt to be used whenever this `personality` is used. +The `toolboxes` are the tools that are available to this `personality`. The `toolboxes` should be a list of `filekey` specifying +files of the `filetype` `toolbox`. + +Personalities can be used in two ways. First it can be used standalone with a prompt input from the command line: + +``` +python3 main.py -p GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo "echo this message" +``` + +In this case, `personality` and `task` from `GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo` are used as the +system prompt while the user argument `echo this message` is used as a user prompt. In this use case, the only tools that this +personality has access to is the `toolboxes` specified in the file. + +Personalities can also be used in a `taskflow` to perform tasks. This is done by adding the `personality` to the `agents` field in a `taskflow` file: + +```yaml +taskflow: + - task: + ... + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant + user_prompt: | + Fetch all the open pull requests from `github/codeql` github repository. + You do not need to provide a summary of the results. + toolboxes: + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/github_official +``` + +In this case, the `personality` specified in `agents` provides the system prompt and the user prompt is specified in `user_prompt` field of the task. A big difference in this case is that the `toolboxes` specified in the `task` will overwrite the `toolboxes` that the `personality` has access to. So in the above example, the `GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant` will have access to the `GitHubSecurityLab/seclab-taskflow-agent/toolboxes/github_official` toolbox instead of its own toolbox. It is important to note that the `personalities` toolboxes get *overwritten* in this case, so whenever a `toolboxes` field is provided in a `task`, it'll use the provided toolboxes and `personality` loses access to its own toolboxes. e.g. + +```yaml +taskflow: + - task: + ... + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo + user_prompt: | + echo this + toolboxes: + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/github_official +``` + +In the above `task`, `GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo` will only have access to the `GitHubSecurityLab/seclab-taskflow-agent/toolboxes/github_official` and can no longer access the `GitHubSecurityLab/seclab-taskflow-agent/toolboxes/echo` `toolbox`. (Unless it is added also in the `task` `toolboxes`) + ## Toolboxes -MCP servers that provide tools. Configured through YAML files in `toolboxes/`. +MCP servers that provide tools. Configured through YAML files of `filetype` `toolboxes`. These are files that provide +the type and parameters to start an MCP server. -Example stdio config: +For example, to start a stdio MCP server that are implemented in a python file: ```yaml # stdio mcp server configuration @@ -187,9 +280,38 @@ server_params: SOME: value ``` +In the above, `command` and `args` are just the command and arguments that should be run to start the MCP server. Environment variables can be passed using the `env` field. + +A [streamable](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http) is also supported by specifying the `kind` to `streamable`: + +```yaml +server_params: + kind: streamable + url: https://api.githubcopilot.com/mcp/ + #See https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md + headers: + Authorization: "{{ env GITHUB_AUTH_HEADER }}" + optional_headers: + X-MCP-Toolsets: "{{ env GITHUB_MCP_TOOLSETS }}" + X-MCP-Readonly: "{{ env GITHUB_MCP_READONLY }}" +``` + +You can force certain tools within a `toolbox` to require user confirmation to run. This can be helpful if a tool may perform irreversible actions and should require user approval prior to its use. This is done by including the name of the tool (function) in the MCP server in the `confirm` section: + +```yaml +server_params: + kind: stdio + ... +# the list of tools that you want the framework to confirm with the user before executing +# use this to guard rail any potentially dangerous functions from MCP servers +confirm: + - memcache_clear_cache +``` + ## Taskflows -A sequence of interdependent tasks performed by a set of Agents. Configured through a YAML based [grammar](taskflows/GRAMMAR.md) in [taskflows/](taskflows/). +A sequence of interdependent tasks performed by a set of Agents. Configured through YAML files of `filetype` `taskflow`. +Taskflows supports a number of features, and their details can be found [here](taskflows/GRAMMAR.md). Example: @@ -269,35 +391,104 @@ Taskflows support [Agent handoffs](https://openai.github.io/openai-agents-python See the [taskflow examples](taskflows/examples) for other useful Taskflow patterns such as repeatable and asynchronous templated prompts. -## Notes about the yaml syntax +## Prompt + +Prompts are configured through YAML files of `filetype` `prompt`. They define a reusable prompt that can be referenced in `taskflow` files. -Every personality, toolbox, and taskflow is defined by a YAML file, which -should always include a header like this: +They contain only one field, the `prompt` field, which is used to replace any `{{ PROMPT_ }}` template parameter in a taskflow. For example, the following `prompt`. +```yaml +seclab-taskflow-agent: + version: 1 + filetype: prompt + filekey: GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt + +prompt: | + Tell me more about bananas as well. ``` + +would replace any `{{ PROMPT_GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt }}` template parameter found in the `user_prompt` section in a taskflow: + +```yaml + - task: + agents: + - fruit_expert + user_prompt: | + Tell me more about apples. + + {{ PROMPTS_GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt }} +``` + +becomes: + +```yaml + - task: + agents: + - fruit_expert + user_prompt: | + Tell me more about apples. + + Tell me more about bananas as well. +``` + +## Model configs + +Model configs are configured through YAML files of `filetype` `model_config`. These provide a way to configure model versions. + +```yaml seclab-taskflow-agent: version: 1 - filetype: taskflow - filekey: taskflows/examples/example + filetype: model_config + filekey: GitHubSecurityLab/seclab-taskflow-agent/configs/model_config +models: + gpt_latest: gpt-5 ``` -The "filetype" determines whether the file defines a personality, toolbox, or -taskflow. This means that different types of files can be stored in the same directory. +A `model_config` file can be used in a `taskflow` and the values defined in `models` can then be used throughout. -The "filekey" is a unique name for the file. It is used to allow -cross-referencing between files. For example, a taskflow can reference -a personality by its filekey. Because filekeys are used for -cross-referencing (rather than file paths), it means that you can move -a file to a different directory without breaking the links. This also -means that you can easily import new files by dropping them into a sub-directory. -We recommend including something like your -GitHub "username/reponame" in your filekeys to make them globably unique. +```yaml +model_config: GitHubSecurityLab/seclab-taskflow-agent/configs/model_config -The "version" number in the header should always be 1. It means that the -file uses version 1 of the seclab-taskflow-agent syntax. If we ever need -to make a major change to the syntax, then we'll update the version number. -This will hopefully enable us to make changes without breaking backwards -compatibility. +taskflow: + - task: + model: gpt_latest +``` + +Model version can then be updated by changing `gpt_latest` in the `model_config` file and applied across all taskflows that use the config. + +## Passing environment variables + +Files of types `taskflow` and `toolbox` allow environment variables to be passed using the `env` field: + +```yaml +server_params: + ... + env: + CODEQL_DBS_BASE_PATH: "{{ env CODEQL_DBS_BASE_PATH }}" + # prevent git repo operations on gh codeql executions + GH_NO_UPDATE_NOTIFIER: "Disable" +``` + +For `toolbox`, `env` can be used inside `server_params`. A template of the form `{{ env ENV_VARIABLE_NAME}}` can be used to pass values of the environment variable from the current process to the MCP server. So in the above, the MCP server is run with `GH_NO_UPDATE_NOTIFIER=disable` and passes the value of `CODEQL_DBS_BASE_PATH` from the current process to the MCP server. The templated paramater `{{ env CODEQL_DBS_BASE_PATH}}` is replaced by the value of the environment variable `CODEQL_DBS_BASE_PATH` in the current process. + +Similarly, environment variables can be passed to a `task` in a `taskflow`: + +```yaml +taskflow: + - task: + must_complete: true + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant + user_prompt: | + Store the json array ["apples", "oranges", "bananas"] in the `fruits` memory key. + env: + MEMCACHE_STATE_DIR: "example_taskflow/" + MEMCACHE_BACKEND: "dictionary_file" +``` + +This overwrites the environment variables `MEMCACHE_STATE_DIR` and `MEMCACHE_BACKEND` for the task only. A template `{{ env ENV_VARIABLE_NAME}}` can also be used. + +Note that when using the template `{{ env ENV_VARIABLE_NAME }}`, `ENV_VARIABLE_NAME` must be the name of an environment variable in the current process. ## License diff --git a/taskflows/GRAMMAR.md b/taskflows/GRAMMAR.md index 39e9ecd..409d111 100644 --- a/taskflows/GRAMMAR.md +++ b/taskflows/GRAMMAR.md @@ -1,6 +1,6 @@ # Taskflows -Taskflows are YAML lists of tasks. +Taskflows are YAML lists of tasks. They are specified by the `filetype` `taskflow`. Example: @@ -30,11 +30,16 @@ Note: The exception to this rule are `run` shell tasks. ### Agents -Agents are defined through their own YAML grammar as so called personalities. +Agents define the system prompt to be used for the task. It contains a list of `filekey` pointing to files of `personality` `filetype`. -Example: +For example, to use the `personality` defined in the following: ```yaml +seclab-taskflow-agent: + version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant + personality: | You are a helpful assistant. @@ -48,6 +53,15 @@ toolboxes: - ... ``` +The task should include the `filekey` in its list of `agents`: + +```yaml + - task: + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant + ... +``` + Task agent lists can define one (primary) or more (handoff) agents. Example: @@ -76,7 +90,7 @@ Tasks can optionally specify which Model to use on the configured inference endp This is a user prompt. ``` -Note that model identifiers may differ between OpenAI compatible endpoint providers, make sure you change your model identifier accordingly when switching providers. +Note that model identifiers may differ between OpenAI compatible endpoint providers, make sure you change your model identifier accordingly when switching providers. If not specified, a default LLM model (`gpt-4o`) is used. ### Completion Requirement @@ -93,29 +107,133 @@ Example: ... ``` -### Repeated Prompts +### Running templated tasks in a loop -Tasks can define templated repeated prompts. A repeated prompt requires the last result output of the previous task in the taskflow to be a json formatted iterable. +Often we may want to iterate through the same tasks with different inputs. For example, we may want to do fetch all the functions from a code base and then analyze each of the function. This can be done using two consecutive task and with the help of the `repeat_prompt` field. -Example: +```yaml + - task: + agents: + - assistant + user_prompt: | + Fetch all the functions in the code base and create a list with entries of the form {'name' : , 'body' : } + - task: + repeat_prompt: true + agents: + - c_auditer + user_prompt: | + The function has name {{ RESULT_name }} and body {{ RESULT_body }} analyze the function. +``` + +In the above, the first task fetches functions in the code base and create a json list object, with each entry having a `name` and `body` field. In the next task, `repeat_prompt` is set to true, meaning that a task is created for each individual object in the list and the object fields are referenced in the templated prompt using `{{ RESULT_ }}`. In other words, `{{ RESULT_name }}` in the prompt is replaced with the value of the `name` field of the object etc. For example, if the list of functions fetched from the first task is: + +```javascript +[{'name' : foo, 'body' : foo(){return 1;}}, {'name' : bar, 'body' : bar(a) {return a + 1;}}] +``` + +Then the tasks created will have their prompts replaced by: + +```yaml + The function has name foo and body foo(){return 1;} analyze the function. +``` + +etc. + +Note that when using `repeat_prompt`, the last tool call result of the previous task is used as the iterable. It is recommended to keep the task that creates the iterable short and simple (e.g. just make one tool call to fetch a list of results) to avoid wrong results being passed to the repeat prompt. + +The iterable can also contain a list of primitives like string or number, in which case, the template `{{ RESULT }}` can be used in the `repeat_prompt` prompt to parse the results instead: + +```yaml + - task: + max_steps: 5 + must_complete: true + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant + user_prompt: | + Store the json array [1, 2, 3] in memory under the + `test_repeat_prompt` key as a json object, then retrieve + the contents of the `test_repeat_prompt` key from memory + ... + - task: + # if the last mcp tool result is iterable + # repeat_prompt can iter those results + must_complete: true + repeat_prompt: true + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant + user_prompt: | + What is the integer value of {{ RESULT }}? +``` + +Repeat prompt can be run in parallel by setting the `async` field to `true`: + +```yaml + - task: + repeat_prompt: true + async: true + agents: + - c_auditer + user_prompt: | + The function has name {{ RESULT_name }} and body {{ RESULT_body }} analyze the function. +``` + +An optional limit can be set to limit the number of asynchronous tasks via `async_limit`. If not set, the default value (5) is used. + +```yaml + - task: + repeat_prompt: true + async: true + async_limit: 3 + agents: + - c_auditer + user_prompt: | + The function has name {{ RESULT_name }} and body {{ RESULT_body }} analyze the function. +``` + +Both `async` and `async_limit` have no effect when use outside of a `repeat_prompt`. + +At the moment, we do not support nested `repeat_prompt`. So the following is not allowed: ```yaml - # this task must result in a json iterable - task: + repeat_prompt: true + agents: + - c_auditer + user_prompt: | + The function has name {{ RESULT_name }} and body {{ RESULT_body }} analyze the function. + - task: + repeat_prompt: true ... - # this task can iterate across the iterable +``` + +#### Shell Tasks + +Tasks can be entirely shell based through the run directive. This simply runs a shell command and pass the result directly to the next task. It is used for creating iterable results for `repeat_prompt`. + +For example: + +```yaml - task: + must_complete: true + run: | + echo '["apple", "banana", "orange"]' + - task: + repeat_prompt: true agents: - assistant - repeat_prompt: true user_prompt: | - This is a templated prompt. It can iterate the results in a list via {{ RESULT }}, - if the result is a dict you access its keys via {{ RESULT_key }}. + What kind of fruit is {{ RESULT }}? ``` -### Context Exclusion +The string `["apple", "banana", "orange"]` is then passed directly to the next task. + +This allows you to e.g. pass in json iterable outputs from shellscripts into a prompt task. + +Use shell tasks when you want to iterate on results that don't need to be generated via a tool call. -Tasks can specify that their tool results and output should be available at the Agent level but not included in the Model context, to e.g. save on tokens for data that does not need to be available to the Model but that still needs to be generated via a prompt inferred tool call. +#### Context Exclusion + +Often when creating iterable results for a `repeat_prompt`, a large iterable is created and we do not want it to be passed to the LLM model because it can easily exceed the token limit. In this case, tasks can specify that their tool results and output should be available at the Agent level but not included in the Model context using the `exclude_from_context` field. Example: @@ -132,7 +250,42 @@ Example: ### Toolboxes / MCP Servers -Toolboxes are MCP server configurations. They can be defined at the Agent level or overridden at the task level. These MCP servers are started and made available to the Agents in the Agents list during a Task. +Toolboxes are MCP server configurations. They can be defined at the Agent level or overridden at the task level. These MCP servers are started and made available to the Agents in the Agents list during a Task. The `toolboxes` field should contain a list of `filekey` for the `toolboxes` that are available for the task: + +```yaml + - task: + ... + toolboxes: + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/codeql +``` + +If no `toolboxes` is specified, then the `toolboxes` defined in the `personality` of the `agent` is used: + +```yaml + - task: + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer + user_prompt: | + List all the files in the codeql database `some/codeql/db`. + - task: +``` + +In the above `task`, as no `toolboxes` is specified, the `toolboxes` defined in the `personality` of `GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer` is used. + +Note that when `toolboxes` is defined for a task, it *overwrites* the `toolboxes` that are available. For example, in the following `task`: + +```yaml + - task: + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer + user_prompt: | + List all the files in the codeql database `some/codeql/db`. + toolboxes: + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/echo + +``` + +For this task, the `agent` `GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer` will have access to the `GitHubSecurityLab/seclab-taskflow-agent/toolboxes/echo` tool. ### Headless Runs @@ -171,68 +324,99 @@ Example: MEMCACHE_BACKEND: "dictionary_file" ``` -### Shell Tasks +### Globals -Tasks can be entirely shell based through the run directive. This allows you to e.g. pass in json iterable outputs from shellscripts into a prompt task. +Taskflows can define toplevel global variables available to every task. Example: ```yaml +globals: + fruit: bananas +taskflow: - task: - must_complete: true - run: | - echo '["apple", "banana", "orange"]' - - task: - repeat_prompt: true agents: - - assistant + - fruit_expert user_prompt: | - What kind of fruit is {{ RESULT }}? + Tell me more about {{ GLOBALS_fruit }}. ``` -Use shell tasks when you want to iterate on results that don't need to be generated via a prompt inferred tool call. - -### Async Tasks +### Reusable Tasks -Tasks can be asynchronous with a specified number of async workers. This is primarily useful to parallelize repeated prompt tasks. +Tasks can reuse single step taskflows and optionally override any of its configurations. This is done by setting a `uses` field with the `filekey` of the single step taskflow as its value. Example: ```yaml - task: - must_complete: true - run: | - echo '["apple", "banana", "orange"]' + uses: single_step_taskflow + model: gpt-4o +``` + +In this case, the prompt and settings of `single_step_taskflow` is used. However, the `model` parameter is overwritten by `gpt-4o`. For example, if `single_step_taskflow` looks like this: + +```yaml +taskflow: - task: - repeat_prompt: true - async: true - async_limit: 3 agents: - - assistant + - some_agent + model: + gpt-4.1 user_prompt: | - What kind of fruit is {{ RESULT }}? + some actions + toolboxes: + - some_toolboxes ``` -### Globals +Then the `task` that uses it effectively becomes: +```yaml + - task: + agents: + - some_agent + model: + gpt-4o + user_prompt: | + some actions + toolboxes: + - some_toolboxes +``` -Taskflows can define toplevel global variables available to every task. +which all settings inherited from `single_step_taskflow` while `model` is overwritten. -Example: +Any `taskflow` that contains only a single step can be used as a reusable taskflow. + +A reusable taskflow can also have templated prompt that takes inputs from its user. This is specified with the `inputs` field from the user. + +```yaml + - task: + uses: single_step_taskflow + inputs: + fruit: apples +``` ```yaml -globals: - fruit: bananas -taskflow: - task: agents: - fruit_expert user_prompt: | - Tell me more about {{ GLOBALS_fruit }}. + Tell me more about {{ INPUTS_fruit }}. +``` + +In this case, the template parameter `{{ INPUTS_fruit }}` is replaced by the value of `fruit` from the `inputs` of the user, which is apples in this case: + +```yaml + - task: + agents: + - fruit_expert + user_prompt: | + Tell me more about apples. ``` -### Inputs +### Reusable Prompts + +Reusable prompts are defined in files of `filetype` `prompts`. These are like macros that gets replaced when a templated parameter of the form `{{ PROMPTS_ }}` is encountered. -Tasks can be provided with templated inputs. +Tasks can incorporate templated prompts which are then replaced by the actual prompt. For example: Example: @@ -240,17 +424,24 @@ Example: - task: agents: - fruit_expert - inputs: - fruit: apples user_prompt: | - Tell me more about {{ INPUTS_fruit }}. + Tell me more about apples. + + {{ PROMPTS_GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt }} ``` +and `GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt` is the following: -### Reusable Prompts +```yaml +seclab-taskflow-agent: + version: 1 + filetype: prompt + filekey: GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt -Tasks can incorporate templated prompts from the prompts directory tree. +prompt: | + Tell me more about bananas as well. +``` -Example: +Then the actual task becomes: ```yaml - task: @@ -259,17 +450,44 @@ Example: user_prompt: | Tell me more about apples. - {{ PROMPTS_examples/example_prompt }} + Tell me more about bananas as well. ``` -### Reusable Tasks +### Model config -Tasks can reuse single step taskflows and optionally override any of its configurations. +LLM models can be configured in a taskflow by setting the `model_config` field to the `filekey` of a file of `filetype` `model_config` : -Example: +```yaml +seclab-taskflow-agent: + version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/CVE-2023-2283/CVE-2023-2283 + +model_config: GitHubSecurityLab/seclab-taskflow-agent/configs/model_config + +``` + +The variables defined in the `model_config` file can then be used throughout the taskflow, e.g. + +```yaml +seclab-taskflow-agent: + version: 1 + filetype: model_config + filekey: GitHubSecurityLab/seclab-taskflow-agent/configs/model_config +models: + gpt_latest: gpt-5 +``` + +When `gpt_latest` is used in the taskflow to specify a model, the value `gpt-5` is used: ```yaml - task: - uses: single_step_taskflow - model: gpt-4o + model: gpt_latest + must_complete: false + agents: + - GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer + user_prompt: | + ``` + +This provides a easy way to update model versions in a taskflow. \ No newline at end of file