diff --git a/.github/workflows/smoketest.yaml b/.github/workflows/smoketest.yaml new file mode 100644 index 0000000..e3057ee --- /dev/null +++ b/.github/workflows/smoketest.yaml @@ -0,0 +1,56 @@ +name: Smoke test - run the examples to check for errors + +on: + issue_comment: + types: [created] # Add "smoke test" as a PR comment to trigger this workflow. + +permissions: + contents: read + +jobs: + Linux: + runs-on: ubuntu-latest + steps: + - name: branch-deploy + id: branch-deploy + uses: github/branch-deploy@48285b12b35e47e2dde0c27d2abb33daa846d98b # v11.0.0 + with: + trigger: "smoke test" + reaction: "eyes" + stable_branch: "main" + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Python venv + run: | + python -m venv .venv + source .venv/bin/activate + python -m pip install -r requirements.txt + + - name: Run tests + env: + COPILOT_TOKEN: ${{ secrets.COPILOT_TOKEN }} + run: | + source .venv/bin/activate + python main.py -p GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant 'explain modems to me please' + python main.py -p GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer 'explain modems to me please' + python main.py -p GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo 'explain modems to me please' + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/CVE-2023-2283/CVE-2023-2283 + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/echo + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_globals + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_inputs + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_large_list_result_iter + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_repeat_prompt + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_repeat_prompt_async + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_repeat_prompt_dictionary + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_reusable_prompt + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_reusable_taskflows + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_triage_taskflow + python main.py -t GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/single_step_taskflow diff --git a/README.md b/README.md index 7452f9f..35f5b91 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,11 @@ Example: ```yaml # personalities define the system prompt level directives for this Agent +seclab-taskflow-agent: + version: 1 + filetype: personality + filekey: personalities/examples/echo + personality: | You are a simple echo bot. You use echo tools to echo things. @@ -157,7 +162,7 @@ task: | # personality toolboxes map to mcp servers made available to this Agent toolboxes: - - echo + - toolboxes/echo ``` ## Toolboxes @@ -168,6 +173,11 @@ Example stdio config: ```yaml # stdio mcp server configuration +seclab-taskflow-agent: + version: 1 + filetype: toolbox + filekey: toolboxes/echo + server_params: kind: stdio command: python @@ -184,6 +194,11 @@ A sequence of interdependent tasks performed by a set of Agents. Configured thro Example: ```yaml +seclab-taskflow-agent: + version: 1 + filetype: taskflow + filekey: taskflows/examples/example.yaml + taskflow: - task: # taskflows can optionally choose any of the support CAPI models for a task @@ -194,18 +209,14 @@ taskflow: must_complete: true # taskflows can set a primary (first entry) and handoff (additional entries) agent agents: - - c_auditer - - fruit_expert + - personalities/c_auditer.yaml + - personalities/examples/fruit_expert.yaml user_prompt: | Store an example vulnerable C program that uses `strcpy` in the `vulnerable_c_example` memory key and explain why `strcpy` is insecure in the C programming language. Do this before handing off to any other agent. - Then provide a summary of a high impact CVE ID that involved a `strcpy` - based buffer overflow based on your GHSA knowledge as an additional - example. - Finally, why are apples and oranges healthy to eat? # taskflows can set temporary environment variables, these support the general @@ -217,16 +228,16 @@ taskflow: MEMCACHE_STATE_DIR: "example_taskflow/" MEMCACHE_BACKEND: "dictionary_file" # taskflows can optionally override personality toolboxes, in this example - # kevin normally only has the memcache toolbox, but we extend it here with + # this normally only has the memcache toolbox, but we extend it here with # the GHSA toolbox toolboxes: - - ghsa - - memcache + - toolboxes/memcache.yaml + - toolboxes/codeql.yaml - task: must_complete: true model: gpt-4.1 agents: - - c_auditer + - personalities/c_auditer.yaml user_prompt: | Retrieve C code for security review from the `vulnerable_c_example` memory key and perform a review. @@ -236,13 +247,58 @@ taskflow: MEMCACHE_STATE_DIR: "example_taskflow/" MEMCACHE_BACKEND: "dictionary_file" toolboxes: - - memcache + - toolboxes/memcache.yaml + # headless mode does not prompt for tool call confirms configured for a server + # note: this will auto-allow, if you want control over potentially dangerous + # tool calls, then you should NOT run a task in headless mode (default: false) + headless: true + - task: + # tasks can also run shell scripts that return e.g. json output for repeat prompt iterable + must_complete: true + run: | + echo '["apple", "banana", "orange"]' + - task: + repeat_prompt: true + agents: + - personalities/assistant.yaml + user_prompt: | + What kind of fruit is {{ RESULT }}? ``` Taskflows support [Agent handoffs](https://openai.github.io/openai-agents-python/handoffs/). Handoffs are useful for implementing triage patterns where the primary Agent can decide to handoff a task to any subsequent Agents in the `Agents` list. See the [taskflow examples](taskflows/examples) for other useful Taskflow patterns such as repeatable and asynchronous templated prompts. +## Notes about the yaml syntax + +Every personality, toolbox, and taskflow is defined by a YAML file, which +should always include a header like this: + +``` +seclab-taskflow-agent: + version: 1 + filetype: taskflow + filekey: taskflows/examples/example +``` + +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. + +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. + +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. + ## License This project is licensed under the terms of the MIT open source license. Please refer to the [LICENSE](./LICENSE) file for the full terms. diff --git a/available_tools.py b/available_tools.py index e2c87c5..273ed34 100644 --- a/available_tools.py +++ b/available_tools.py @@ -3,9 +3,18 @@ class VersionException(Exception): pass +class FileIDException(Exception): + pass + class FileTypeException(Exception): pass +def add_yaml_to_dict(table, key, yaml): + """Add the yaml to the table, but raise an error if the id isn't unique """ + if key in table: + raise FileIDException(str(key)) + table.update({key: yaml}) + class AvailableTools: """ This class is used for storing dictionaries of all the available @@ -30,20 +39,23 @@ def __init__(self, yamls: dict): version = header['version'] if version != 1: raise VersionException(str(version)) - filetype = header['type'] + filekey = header['filekey'] + filetype = header['filetype'] if filetype == 'personality': - self.personalities.update({path: yaml}) + add_yaml_to_dict(self.personalities, filekey, yaml) elif filetype == 'taskflow': - self.taskflows.update({path: yaml}) + add_yaml_to_dict(self.taskflows, filekey, yaml) elif filetype == 'prompt': - self.prompts.update({path: yaml}) + add_yaml_to_dict(self.prompts, filekey, yaml) elif filetype == 'toolbox': - self.toolboxes.update({path: yaml}) + add_yaml_to_dict(self.toolboxes, filekey, yaml) else: raise FileTypeException(str(filetype)) except KeyError as err: logging.error(f'{path} does not contain the key {err.args[0]}') except VersionException as err: logging.error(f'{path}: seclab-taskflow-agent version {err.args[0]} is not supported') + except FileIDException as err: + logging.error(f'{path}: file ID {err.args[0]} is not unique') except FileTypeException as err: logging.error(f'{path}: seclab-taskflow-agent file type {err.args[0]} is not supported') diff --git a/main.py b/main.py index fed125a..fbb329f 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,7 @@ import re import json import uuid +import pathlib from agent import DEFAULT_MODEL, TaskRunHooks, TaskAgentHooks #from agents.run import DEFAULT_MAX_TURNS # XXX: this is 10, we need more than that @@ -23,7 +24,7 @@ from typing import Any from shell_utils import shell_tool_call -from mcp_utils import DEFAULT_MCP_CLIENT_SESSION_TIMEOUT, ReconnectingMCPServerStdio, AsyncDebugMCPServerStdio, MCPNamespaceWrap, mcp_client_params, mcp_system_prompt, StreamableMCPThread +from mcp_utils import DEFAULT_MCP_CLIENT_SESSION_TIMEOUT, ReconnectingMCPServerStdio, AsyncDebugMCPServerStdio, MCPNamespaceWrap, mcp_client_params, mcp_system_prompt, StreamableMCPThread, compress_name from render_utils import render_model_output, flush_async_output from env_utils import TmpEnv from yaml_parser import YamlParser @@ -255,7 +256,7 @@ async def mcp_session_task( for handoff_agent in list(agents.keys())[1:]: handoffs.append(TaskAgent( # XXX: name has to be descriptive for an effective handoff - name=handoff_agent, + name=compress_name(handoff_agent), instructions=prompt_with_handoff_instructions( mcp_system_prompt( agents[handoff_agent]['personality'], @@ -399,7 +400,7 @@ async def on_handoff_hook( if p: personality = available_tools.personalities.get(p) if personality is None: - raise ValueError("No such personality!") + raise ValueError(f"No such personality: {p}") await deploy_task_agents( available_tools, @@ -413,7 +414,7 @@ async def on_handoff_hook( taskflow = available_tools.taskflows.get(t) if taskflow is None: - raise ValueError("No such taskflow!") + raise ValueError(f"No such taskflow: {t}") await render_model_output(f"** 🤖💪 Running Task Flow: {t}\n") @@ -628,11 +629,12 @@ async def _deploy_task_agents(resolved_agents, prompt): break if __name__ == '__main__': + cwd = pathlib.Path.cwd() available_tools = AvailableTools( - YamlParser('personalities').get_yaml_dict() | - YamlParser('taskflows').get_yaml_dict() | - YamlParser('prompts').get_yaml_dict(dir_namespace=True) | - YamlParser('toolboxes').get_yaml_dict(recurse=True)) + YamlParser(cwd).get_yaml_dict((cwd/'personalities').rglob('*')) | + YamlParser(cwd).get_yaml_dict((cwd/'taskflows').rglob('*')) | + YamlParser(cwd).get_yaml_dict((cwd/'prompts').rglob('*')) | + YamlParser(cwd).get_yaml_dict((cwd/'toolboxes').rglob('*'))) p, t, l, user_prompt, help_msg = parse_prompt_args(available_tools) diff --git a/mcp_utils.py b/mcp_utils.py index dd64909..c1fb40e 100644 --- a/mcp_utils.py +++ b/mcp_utils.py @@ -9,6 +9,7 @@ import os import socket import signal +import hashlib from urllib.parse import urlparse from mcp.types import CallToolResult, TextContent @@ -18,6 +19,16 @@ DEFAULT_MCP_CLIENT_SESSION_TIMEOUT = 120 +# The openai API complains if the name of a tool is longer than 64 +# chars. But we're encouraging people to use long descriptive +# filekeys to avoid accidental collisions, so it's very easy to go +# over the limit. So this function converts a name to a 12 character +# hash. +def compress_name(name): + m = hashlib.sha256() + m.update(name.encode('utf-8')) + return m.hexdigest()[:12] + # A process management class for running in-process MCP streamable servers class StreamableMCPThread(Thread): """Process management for local streamable MCP servers""" @@ -221,7 +232,7 @@ class MCPNamespaceWrap: def __init__(self, confirms, obj): self.confirms = confirms self._obj = obj - self.namespace = f"{obj.name.upper().replace(' ', '_')}_" + self.namespace = compress_name(obj.name) def __getattr__(self, name): attr = getattr(self._obj, name) diff --git a/personalities/assistant.yaml b/personalities/assistant.yaml index 3069859..6d3bb35 100644 --- a/personalities/assistant.yaml +++ b/personalities/assistant.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant personality: | You are a helpful assistant. diff --git a/personalities/c_auditer.yaml b/personalities/c_auditer.yaml index 3d1f6ba..556b73b 100644 --- a/personalities/c_auditer.yaml +++ b/personalities/c_auditer.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer personality: | Your name is Ronald. You are a C programming language security expert. @@ -14,5 +15,5 @@ task: | your findings where possible. toolboxes: - - memcache - - codeql + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/codeql diff --git a/personalities/examples/apple_expert.yaml b/personalities/examples/apple_expert.yaml index 84eb1db..16683fe 100644 --- a/personalities/examples/apple_expert.yaml +++ b/personalities/examples/apple_expert.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/apple_expert personality: | You are an apples expert. diff --git a/personalities/examples/banana_expert.yaml b/personalities/examples/banana_expert.yaml index d37f038..edb09de 100644 --- a/personalities/examples/banana_expert.yaml +++ b/personalities/examples/banana_expert.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/banana_expert personality: | You are a bananas expert. diff --git a/personalities/examples/echo.yaml b/personalities/examples/echo.yaml index 2c080e8..25152c9 100644 --- a/personalities/examples/echo.yaml +++ b/personalities/examples/echo.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo personality: | You are a simple echo bot. You use echo tools to echo things. @@ -9,5 +10,5 @@ task: | Echo user inputs using the echo tools. toolboxes: - - echo + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/echo diff --git a/personalities/examples/example_triage_agent.yaml b/personalities/examples/example_triage_agent.yaml index fd09711..246a414 100644 --- a/personalities/examples/example_triage_agent.yaml +++ b/personalities/examples/example_triage_agent.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/example_triage_agent personality: | You are a triage agent. You route tasks to other agents. diff --git a/personalities/examples/fruit_expert.yaml b/personalities/examples/fruit_expert.yaml index 12f0f31..e987904 100644 --- a/personalities/examples/fruit_expert.yaml +++ b/personalities/examples/fruit_expert.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/fruit_expert personality: | Your name is Bob. You are a fruit expert. diff --git a/personalities/examples/orange_expert.yaml b/personalities/examples/orange_expert.yaml index 63153b3..aa4b953 100644 --- a/personalities/examples/orange_expert.yaml +++ b/personalities/examples/orange_expert.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: personality version: 1 + filetype: personality + filekey: GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/orange_expert personality: | You are an oranges expert. diff --git a/prompts/examples/example_prompt.yaml b/prompts/examples/example_prompt.yaml index ebeca8c..1e9f38b 100644 --- a/prompts/examples/example_prompt.yaml +++ b/prompts/examples/example_prompt.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: prompt version: 1 + filetype: prompt + filekey: GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt prompt: | Tell me more about bananas as well. diff --git a/taskflows/CVE-2023-2283/CVE-2023-2283.yaml b/taskflows/CVE-2023-2283/CVE-2023-2283.yaml index f3a018a..9f30f29 100644 --- a/taskflows/CVE-2023-2283/CVE-2023-2283.yaml +++ b/taskflows/CVE-2023-2283/CVE-2023-2283.yaml @@ -1,22 +1,23 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/CVE-2023-2283/CVE-2023-2283 taskflow: - task: must_complete: true headless: true agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | Clear the memory cache. toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache - task: model: gpt-4.1 must_complete: false agents: - - c_auditer + - GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer user_prompt: | You are auditing code using the `libssh-mirror/libssh-codeql` CodeQL database. @@ -76,15 +77,15 @@ taskflow: 6. Make small and concise single line notes while you work. Update the existing value for `notes` in memory as you work. toolboxes: - - codeql - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/codeql + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache - task: must_complete: true agents: - - c_auditer + - GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer user_prompt: | Fetch your audit notes from memory using the `notes` key. Do not perform any additional security review, only show me your notes. toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache diff --git a/taskflows/examples/echo.yaml b/taskflows/examples/echo.yaml index 1174f7a..c01334a 100644 --- a/taskflows/examples/echo.yaml +++ b/taskflows/examples/echo.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/echo taskflow: - task: @@ -8,13 +9,13 @@ taskflow: max_steps: 5 must_complete: true agents: - - echo + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo user_prompt: | Hello - task: must_complete: true agents: - - echo + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/echo user_prompt: | Goodbye env: diff --git a/taskflows/examples/example.yaml b/taskflows/examples/example.yaml index 24fc07a..15203dd 100644 --- a/taskflows/examples/example.yaml +++ b/taskflows/examples/example.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example taskflow: - task: @@ -12,8 +13,8 @@ taskflow: must_complete: true # taskflows can set a primary (first entry) and handoff (additional entries) agent agents: - - c_auditer - - fruit_expert + - GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/fruit_expert user_prompt: | Store an example vulnerable C program that uses `strcpy` in the `vulnerable_c_example` memory key and explain why `strcpy` @@ -34,13 +35,13 @@ taskflow: # this normally only has the memcache toolbox, but we extend it here with # the GHSA toolbox toolboxes: - - memcache - - codeql + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/codeql - task: must_complete: true model: gpt-4.1 agents: - - c_auditer + - GitHubSecurityLab/seclab-taskflow-agent/personalities/c_auditer user_prompt: | Retrieve C code for security review from the `vulnerable_c_example` memory key and perform a review. @@ -50,7 +51,7 @@ taskflow: MEMCACHE_STATE_DIR: "example_taskflow/" MEMCACHE_BACKEND: "dictionary_file" toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache # headless mode does not prompt for tool call confirms configured for a server # note: this will auto-allow, if you want control over potentially dangerous # tool calls, then you should NOT run a task in headless mode (default: false) @@ -63,6 +64,6 @@ taskflow: - task: repeat_prompt: true agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | What kind of fruit is {{ RESULT }}? diff --git a/taskflows/examples/example_globals.yaml b/taskflows/examples/example_globals.yaml index c63d433..24c9864 100644 --- a/taskflows/examples/example_globals.yaml +++ b/taskflows/examples/example_globals.yaml @@ -1,13 +1,13 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_globals globals: fruit: bananas taskflow: - task: agents: - - fruit_expert + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/fruit_expert user_prompt: | Tell me more about {{ GLOBALS_fruit }}. - diff --git a/taskflows/examples/example_inputs.yaml b/taskflows/examples/example_inputs.yaml index 8cc136c..5c6d255 100644 --- a/taskflows/examples/example_inputs.yaml +++ b/taskflows/examples/example_inputs.yaml @@ -1,11 +1,12 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_inputs taskflow: - task: agents: - - fruit_expert + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/fruit_expert inputs: fruit: apples user_prompt: | diff --git a/taskflows/examples/example_large_list_result_iter.yaml b/taskflows/examples/example_large_list_result_iter.yaml index 2733351..8a58253 100644 --- a/taskflows/examples/example_large_list_result_iter.yaml +++ b/taskflows/examples/example_large_list_result_iter.yaml @@ -1,24 +1,25 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_large_list_result_iter taskflow: - task: exclude_from_context: true must_complete: true agents: - - assistant + - 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: - - github_official + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/github_official env: GITHUB_MCP_TOOLSETS: pull_requests - task: must_complete: true repeat_prompt: true agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | Echo this: The title is {{ RESULT_title }} and the url is {{ RESULT_url }}. diff --git a/taskflows/examples/example_repeat_prompt.yaml b/taskflows/examples/example_repeat_prompt.yaml index 4347aef..470dc48 100644 --- a/taskflows/examples/example_repeat_prompt.yaml +++ b/taskflows/examples/example_repeat_prompt.yaml @@ -1,13 +1,14 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_repeat_prompt taskflow: - task: max_steps: 5 must_complete: true agents: - - assistant + - 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 @@ -16,13 +17,13 @@ taskflow: MEMCACHE_STATE_DIR: "example_repeat_prompt_taskflow/" MEMCACHE_BACKEND: "dictionary_file" toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache - task: # if the last mcp tool result is iterable # repeat_prompt can iter those results must_complete: true repeat_prompt: true agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | What is the integer value of {{ RESULT }}? diff --git a/taskflows/examples/example_repeat_prompt_async.yaml b/taskflows/examples/example_repeat_prompt_async.yaml index 6897dc4..c5f40fd 100644 --- a/taskflows/examples/example_repeat_prompt_async.yaml +++ b/taskflows/examples/example_repeat_prompt_async.yaml @@ -1,13 +1,14 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_repeat_prompt_async taskflow: - task: max_steps: 5 must_complete: true agents: - - assistant + - 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 @@ -16,7 +17,7 @@ taskflow: MEMCACHE_STATE_DIR: "example_repeat_prompt_taskflow/" MEMCACHE_BACKEND: "dictionary_file" toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache - task: # if the last mcp tool result is iterable # repeat_prompt can iter those results @@ -27,6 +28,6 @@ taskflow: # you can also limit the max concurrent tasks (default 5) async_limit: 2 agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | What is the integer value of {{ RESULT }}? diff --git a/taskflows/examples/example_repeat_prompt_dictionary.yaml b/taskflows/examples/example_repeat_prompt_dictionary.yaml index 37545b4..f26d2e8 100644 --- a/taskflows/examples/example_repeat_prompt_dictionary.yaml +++ b/taskflows/examples/example_repeat_prompt_dictionary.yaml @@ -1,13 +1,14 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_repeat_prompt_dictionary taskflow: - task: max_steps: 5 must_complete: true agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | Store the json array [{index : 1, value : 2}, {index : 2, value : 4}, {index : 3, value : 6}] in memory under the `test_repeat_prompt` key @@ -17,13 +18,13 @@ taskflow: MEMCACHE_STATE_DIR: "example_repeat_prompt_taskflow/" MEMCACHE_BACKEND: "dictionary_file" toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache - task: # if the last mcp tool result is iterable # repeat_prompt can iter those results must_complete: true repeat_prompt: true agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | What is the value of {{ RESULT_index }} + {{ RESULT_value }}? diff --git a/taskflows/examples/example_reusable_prompt.yaml b/taskflows/examples/example_reusable_prompt.yaml index 799b8bb..a1c220a 100644 --- a/taskflows/examples/example_reusable_prompt.yaml +++ b/taskflows/examples/example_reusable_prompt.yaml @@ -1,12 +1,13 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_reusable_prompt taskflow: - task: agents: - - fruit_expert + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/fruit_expert user_prompt: | Tell me more about apples. - {{ PROMPTS_examples/example_prompt }} + {{ GitHubSecurityLab/seclab-taskflow-agent/prompts/examples/example_prompt.yaml }} diff --git a/taskflows/examples/example_reusable_taskflows.yaml b/taskflows/examples/example_reusable_taskflows.yaml index 6d45314..33cc43c 100644 --- a/taskflows/examples/example_reusable_taskflows.yaml +++ b/taskflows/examples/example_reusable_taskflows.yaml @@ -1,10 +1,11 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_reusable_taskflows taskflow: - task: # with the `uses` directive we can reuse single task taskflows - uses: single_step_taskflow + uses: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/single_step_taskflow # and optionally override any of its configurations model: gpt-4o diff --git a/taskflows/examples/example_triage_taskflow.yaml b/taskflows/examples/example_triage_taskflow.yaml index 7910b20..4ae689b 100644 --- a/taskflows/examples/example_triage_taskflow.yaml +++ b/taskflows/examples/example_triage_taskflow.yaml @@ -1,40 +1,41 @@ # a simple example of the triage Agent pattern seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/example_triage_taskflow taskflow: - task: must_complete: true agents: - - assistant + - 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" toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache - task: must_complete: true agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | Retrieve the contents of the `fruits` memory key. env: MEMCACHE_STATE_DIR: "example_taskflow/" MEMCACHE_BACKEND: "dictionary_file" toolboxes: - - memcache + - GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache - task: model: gpt-4.1 repeat_prompt: true agents: # primary agent for this task - - example_triage_agent + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/example_triage_agent # handoff agents - - apple_expert - - orange_expert - - banana_expert + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/apple_expert + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/orange_expert + - GitHubSecurityLab/seclab-taskflow-agent/personalities/examples/banana_expert user_prompt: | Tell me more about how {{ RESULT }} are grown. diff --git a/taskflows/examples/single_step_taskflow.yaml b/taskflows/examples/single_step_taskflow.yaml index fef5002..3914ac8 100644 --- a/taskflows/examples/single_step_taskflow.yaml +++ b/taskflows/examples/single_step_taskflow.yaml @@ -1,11 +1,12 @@ seclab-taskflow-agent: - type: taskflow version: 1 + filetype: taskflow + filekey: GitHubSecurityLab/seclab-taskflow-agent/taskflows/examples/single_step_taskflow taskflow: - task: model: gpt-4.1 agents: - - assistant + - GitHubSecurityLab/seclab-taskflow-agent/personalities/assistant user_prompt: | Explain the plot of William Gibson's Neuromancer in a single paragraph. diff --git a/toolboxes/codeql.yaml b/toolboxes/codeql.yaml index b8002c3..865e9fa 100644 --- a/toolboxes/codeql.yaml +++ b/toolboxes/codeql.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: toolbox version: 1 + filetype: toolbox + filekey: GitHubSecurityLab/seclab-taskflow-agent/toolboxes/codeql server_params: kind: streamable diff --git a/toolboxes/echo.yaml b/toolboxes/echo.yaml index b4ca510..2dc3c99 100644 --- a/toolboxes/echo.yaml +++ b/toolboxes/echo.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: toolbox version: 1 + filetype: toolbox + filekey: GitHubSecurityLab/seclab-taskflow-agent/toolboxes/echo server_params: kind: stdio diff --git a/toolboxes/github_official.yaml b/toolboxes/github_official.yaml index 0bfa367..e5f718c 100644 --- a/toolboxes/github_official.yaml +++ b/toolboxes/github_official.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: toolbox version: 1 + filetype: toolbox + filekey: GitHubSecurityLab/seclab-taskflow-agent/toolboxes/github_official server_params: kind: streamable diff --git a/toolboxes/logbook.yaml b/toolboxes/logbook.yaml index ef46808..c7142a4 100644 --- a/toolboxes/logbook.yaml +++ b/toolboxes/logbook.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: toolbox version: 1 + filetype: toolbox + filekey: GitHubSecurityLab/seclab-taskflow-agent/toolboxes/logbook server_params: kind: stdio diff --git a/toolboxes/memcache.yaml b/toolboxes/memcache.yaml index d6f5036..9a643e7 100644 --- a/toolboxes/memcache.yaml +++ b/toolboxes/memcache.yaml @@ -1,6 +1,7 @@ seclab-taskflow-agent: - type: toolbox version: 1 + filetype: toolbox + filekey: GitHubSecurityLab/seclab-taskflow-agent/toolboxes/memcache server_params: kind: stdio diff --git a/yaml_parser.py b/yaml_parser.py index f02d21f..ea6b0a9 100644 --- a/yaml_parser.py +++ b/yaml_parser.py @@ -1,32 +1,25 @@ import os import yaml from pathlib import Path +from collections.abc import Iterator import logging class YamlParser: - def __init__(self, path: str | Path): - self.path = Path(path) + def __init__(self, rootpath: Path): + self.rootpath = rootpath.absolute() - def get_yaml_dict(self, recurse=True, dir_namespace=False) -> dict: + def get_yaml_dict(self, files: Iterator) -> dict: """Return a directory of yaml files as a dictionary of file name indexed yaml dicts""" - dir_path = Path(__file__).parent.resolve() / Path(self.path) yaml_dict = {} - if recurse: - files = dir_path.rglob('*') - else: - files = list(dir_path.iterdir()) for f in files: if f.is_file() and not f.name.startswith('.') and f.suffix in ['.yaml', '.yml']: try: - with open(os.path.join(dir_path, f)) as stream: + with open(f) as stream: try: p = yaml.safe_load(stream) - # namespace to a full relative path e.g. /subdir/file - if dir_namespace: - namespaced_stem = str(f.relative_to(dir_path).with_suffix('')) - yaml_dict[namespaced_stem] = p - else: - yaml_dict[Path(f).stem] = p + # Use the relative path as the key in the dict. + namespaced_stem = str(f.relative_to(self.rootpath)) + yaml_dict[namespaced_stem] = p except yaml.YAMLError as e: logging.error(e) except FileNotFoundError as e: