Skip to content

Commit 962d12f

Browse files
Merge branch 'main' into p--add-custom-header
2 parents 4b79c8b + 37fc28f commit 962d12f

33 files changed

Lines changed: 1049 additions & 876 deletions

.github/workflows/ci.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ jobs:
3535

3636
- name: Run static analysis
3737
run: |
38-
# hatch fmt --check
39-
echo linter errors will be fixed in a separate PR
38+
hatch fmt --linter --check
4039
4140
- name: Run tests
4241
run: hatch test --python ${{ matrix.python-version }} --cover --randomize --parallel --retries 2 --retry-delay 1

.github/workflows/smoketest.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
if: github.event.issue.pull_request # Make sure the comment is on a PR
2121
outputs:
2222
allowed: ${{ steps.branch-deploy.outputs.continue }}
23+
sha: ${{ steps.branch-deploy.outputs.sha }}
2324
steps:
2425
- name: branch-deploy
2526
id: branch-deploy
@@ -48,9 +49,11 @@ jobs:
4849
- name: Checkout the PR
4950
env:
5051
PR_NUMBER: ${{ github.event.issue.number }}
52+
REF: ${{ needs.permission-check.outputs.sha }}
5153
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5254
run: |
5355
gh pr checkout $PR_NUMBER
56+
git checkout $REF
5457
5558
- name: Setup Python venv
5659
run: |

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2025 GitHub
3+
Copyright GitHub, Inc.
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
# Seclab Taskflow Agent
1+
# GitHub Security Lab Taskflow Agent
22

33
The Security Lab Taskflow Agent is an MCP enabled multi-Agent framework.
44

55
The Taskflow Agent is built on top of the [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/).
66

7-
While the Taskflow Agent does not integrate into the GitHub Dotcom Copilot UX, it does operate using the Copilot API (CAPI) as its backend, similar to Copilot IDE extensions.
8-
97
## Core Concepts
108

119
The Taskflow Agent leverages a GitHub Workflow-esque YAML based grammar to perform a series of tasks using a set of Agents.
@@ -16,7 +14,7 @@ Agents are defined through [personalities](examples/personalities/), that receiv
1614

1715
Agents can cooperate to complete sequences of tasks through so-called [taskflows](doc/GRAMMAR.md).
1816

19-
You can find a detailed overview of the taskflow grammar [here](taskflows/GRAMMAR.md) and example taskflows [here](examples/taskflows/).
17+
You can find a detailed overview of the taskflow grammar [here](doc/GRAMMAR.md) and example taskflows [here](examples/taskflows/).
2018

2119
## Use Cases and Examples
2220

@@ -36,17 +34,26 @@ Python >= 3.9 or Docker
3634

3735
## Configuration
3836

39-
Provide a GitHub token for an account that is entitled to use [GitHub Models](https://models.github.ai) via the `AI_API_TOKEN` environment variable. Further configuration is use case dependent, i.e. pending which MCP servers you'd like to use in your taskflows.
37+
Provide a GitHub token for an account that is entitled to use [GitHub Models](https://models.github.ai) via the `AI_API_TOKEN` environment variable. Further configuration is use case dependent, i.e. pending which MCP servers you'd like to use in your taskflows. In a terminal, you can add `AI_API_TOKEN` to the environment like this:
38+
39+
```sh
40+
export AI_API_TOKEN=<your_github_token>
41+
```
42+
43+
Or, if you are using GitHub Codespaces, then you can [add a Codespace secret](https://github.com/settings/codespaces/secrets/new) so that `AI_API_TOKEN` is automatically available when working in a Codespace.
44+
45+
Many of the MCP servers in the [seclab-taskflow](https://github.com/GitHubSecurityLab/seclab-taskflows) repo also need an environment variable named `GH_TOKEN` for accessing the GitHub API. You can use two separate PATs if you want, or you can use one PAT for both purposes, like this:
46+
47+
```sh
48+
export GH_TOKEN=$AI_API_TOKEN
49+
```
4050

41-
You can set persisting environment variables via an `.env` file in the project root.
51+
We do not recommend storing secrets on disk, but you can persist non-sensitive environment variables by adding a `.env` file in the project root.
4252

4353
Example:
4454

4555
```sh
46-
# Tokens
47-
AI_API_TOKEN=<your_github_token>
4856
# MCP configs
49-
GH_TOKEN=<your_github_token>
5057
CODEQL_DBS_BASE_PATH="/app/my_data/codeql_databases"
5158
AI_API_ENDPOINT="https://models.github.ai/inference"
5259
```
@@ -319,7 +326,7 @@ seclab-taskflow-agent:
319326
320327
taskflow:
321328
- task:
322-
# taskflows can optionally choose any of the support CAPI models for a task
329+
# taskflows can optionally choose any of the models supported by your API for a task
323330
model: gpt-4.1
324331
# taskflows can optionally limit the max allowed number of Agent task loop
325332
# iterations to complete a task, this defaults to 50 when not provided
@@ -385,7 +392,7 @@ taskflow:
385392
386393
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.
387394

388-
See the [taskflow examples](taskflows/examples) for other useful Taskflow patterns such as repeatable and asynchronous templated prompts.
395+
See the [taskflow examples](examples/taskflows) for other useful Taskflow patterns such as repeatable and asynchronous templated prompts.
389396

390397

391398
You can run a taskflow from the command line like this:

pyproject.toml

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ dependencies = [
2929
"annotated-types==0.7.0",
3030
"anyio==4.9.0",
3131
"attrs==25.3.0",
32-
"Authlib==1.6.5",
32+
"Authlib==1.6.6",
3333
"certifi==2025.6.15",
3434
"cffi==2.0.0",
3535
"charset-normalizer==3.4.2",
@@ -104,7 +104,7 @@ dependencies = [
104104
"typing-inspection==0.4.1",
105105
"typing_extensions==4.14.1",
106106
"ujson==5.10.0",
107-
"urllib3==2.6.0",
107+
"urllib3==2.6.3",
108108
"uvicorn==0.35.0",
109109
"zipp==3.23.0",
110110
]
@@ -145,3 +145,92 @@ exclude_lines = [
145145
"if __name__ == .__main__.:",
146146
"if TYPE_CHECKING:",
147147
]
148+
149+
[tool.ruff]
150+
# Set target version to 3.10 to support match statements
151+
target-version = "py310"
152+
153+
[tool.ruff.lint]
154+
# Suppress all current linter errors to establish a baseline
155+
ignore = [
156+
"A001", # Variable shadows built-in
157+
"A002", # Argument shadows built-in
158+
"A004", # Import shadows built-in
159+
"ARG001", # Unused function argument
160+
"B006", # Mutable default argument
161+
"B007", # Unused loop control variable
162+
"B008", # Function call in argument defaults
163+
"B023", # Function uses loop variable
164+
"B904", # raise-without-from-inside-except
165+
"BLE001", # Blind except
166+
"C405", # Unnecessary literal set
167+
"C416", # Unnecessary comprehension
168+
"E721", # Type comparison using ==
169+
"E722", # Bare except
170+
"E741", # Ambiguous variable name
171+
"EM101", # Exception string literal
172+
"EM102", # Exception f-string
173+
"F401", # Unused import
174+
"F541", # f-string without placeholders
175+
"F811", # Redefinition of unused name
176+
"F821", # Undefined name
177+
"F841", # Unused variable
178+
"FA100", # Missing from __future__ import annotations
179+
"FA102", # Missing from __future__ import annotations in stub
180+
"FBT001", # Boolean positional arg in function definition
181+
"FBT002", # Boolean default value in function definition
182+
"FURB188", # Prefer removeprefix over conditional slice
183+
"G004", # Logging with f-string
184+
"I001", # Import block unsorted or unformatted
185+
"INP001", # Implicit namespace package
186+
"LOG015", # root logger usage
187+
"N801", # Class name should use CapWords convention
188+
"N802", # Function name should be lowercase
189+
"N806", # Variable in function should be lowercase
190+
"N818", # Exception name should end with Error
191+
"PERF102", # Use keys() or values() instead of items()
192+
"PERF401", # Use list comprehension
193+
"PIE790", # Unnecessary pass statement
194+
"PLC0415", # Import should be at top of file
195+
"PLC1802", # Use of len(x) == 0
196+
"PLR2004", # Magic value used in comparison
197+
"PLW0602", # Global variable not assigned
198+
"PLW0603", # Using global statement
199+
"PLW1508", # Invalid envvar default
200+
"PLW2901", # Outer loop variable overwritten
201+
"PT011", # pytest.raises too broad
202+
"PYI041", # Use float instead of int | float
203+
"RET503", # Missing explicit return
204+
"RET504", # Unnecessary assignment before return
205+
"RET505", # Unnecessary else after return
206+
"RET506", # Unnecessary else after raise
207+
"RUF005", # Unpack instead of concatenation
208+
"RUF010", # Use explicit conversion flag
209+
"RUF015", # Prefer next() over single element slice
210+
"RUF059", # Use of private function/attribute
211+
"RUF100", # Unused noqa directive
212+
"S108", # Hardcoded temp file/directory
213+
"S607", # Starting process with partial path
214+
"SIM102", # Use single if statement
215+
"SIM115", # Use context handler for file
216+
"SIM210", # Use ternary operator
217+
"SLF001", # Private member access
218+
"T201", # print found
219+
"TID252", # Relative imports from parent modules
220+
"TRY003", # Raise vanilla args
221+
"TRY004", # Prefer TypeError for wrong type
222+
"TRY300", # Consider moving statement to else
223+
"TRY301", # Abstract raise to inner function
224+
"TRY400", # Use logging.exception instead of logging.error
225+
"UP004", # Use X | Y for union types
226+
"UP006", # Use X | Y for union types in isinstance
227+
"UP009", # UTF-8 encoding declaration
228+
"UP015", # Unnecessary mode argument
229+
"UP020", # Use builtin open
230+
"UP024", # Replace aliased errors with OSError
231+
"UP032", # Use f-string
232+
"UP035", # Import from collections.abc
233+
"UP045", # Use X | None for type annotations
234+
"W291", # Trailing whitespace
235+
"W293", # Blank line contains whitespace
236+
]

release_tools/copy_files.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import sys
77
import subprocess
88

9+
910
def read_file_list(list_path):
1011
"""
1112
Reads a file containing file paths, ignoring empty lines and lines starting with '#'.
@@ -15,6 +16,7 @@ def read_file_list(list_path):
1516
lines = [line.strip() for line in f]
1617
return [line for line in lines if line and not line.startswith("#")]
1718

19+
1820
def copy_files(file_list, dest_dir):
1921
"""
2022
Copy files listed in file_list to dest_dir, preserving their relative paths.
@@ -29,6 +31,7 @@ def copy_files(file_list, dest_dir):
2931
shutil.copy2(abs_src, abs_dest)
3032
print(f"Copied {abs_src} -> {abs_dest}")
3133

34+
3235
def ensure_git_repo(dest_dir):
3336
"""
3437
Initializes a git repository in dest_dir if it's not already a git repo.
@@ -56,6 +59,7 @@ def ensure_git_repo(dest_dir):
5659
print(f"Failed to ensure 'main' branch in {dest_dir}: {e}")
5760
sys.exit(1)
5861

62+
5963
def git_add_files(file_list, dest_dir):
6064
"""
6165
Runs 'git add' on each file in file_list within dest_dir.
@@ -72,6 +76,7 @@ def git_add_files(file_list, dest_dir):
7276
finally:
7377
os.chdir(cwd)
7478

79+
7580
if __name__ == "__main__":
7681
if len(sys.argv) != 3:
7782
print("Usage: python copy_files.py <file_list.txt> <dest_dir>")

release_tools/publish_docker.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,34 @@
66
import subprocess
77
import sys
88

9+
910
def get_image_digest(image_name, tag):
1011
result = subprocess.run(
1112
["docker", "buildx", "imagetools", "inspect", f"{image_name}:{tag}"],
12-
stdout=subprocess.PIPE, check=True, text=True
13+
stdout=subprocess.PIPE,
14+
check=True,
15+
text=True,
1316
)
1417
for line in result.stdout.splitlines():
1518
if line.strip().startswith("Digest:"):
1619
return line.strip().split(":", 1)[1].strip()
1720
return None
1821

22+
1923
def build_and_push_image(dest_dir, image_name, tag):
2024
# Build
21-
subprocess.run([
22-
"docker", "buildx", "build", "--platform", "linux/amd64", "-t", f"{image_name}:{tag}", dest_dir
23-
], check=True)
25+
subprocess.run(
26+
["docker", "buildx", "build", "--platform", "linux/amd64", "-t", f"{image_name}:{tag}", dest_dir], check=True
27+
)
2428
# Push
25-
subprocess.run([
26-
"docker", "push", f"{image_name}:{tag}"
27-
], check=True)
29+
subprocess.run(["docker", "push", f"{image_name}:{tag}"], check=True)
2830
print(f"Pushed {image_name}:{tag}")
2931
digest = get_image_digest(image_name, tag)
3032
print(f"Image digest: {digest}")
3133
with open("/tmp/digest.txt", "w") as f:
3234
f.write(digest)
3335

36+
3437
if __name__ == "__main__":
3538
if len(sys.argv) != 3:
3639
print("Usage: python build_and_publish_docker.py <ghcr_username/repo> <tag>")

0 commit comments

Comments
 (0)