Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8ad6ab8
feat: add agents/ directory with orchestration framework
aidandaly24 May 5, 2026
0b9208f
chore: remove pr_reviewer from this PR — migration is a separate task
aidandaly24 May 5, 2026
13964f9
fix: address 14 review findings from multi-agent code review
aidandaly24 May 5, 2026
ff86d2d
perf: switch to targeted testing — only run tests for changed files, …
aidandaly24 May 6, 2026
6912258
fix: extract phase cds into each repo before git diff — fixes 0-file …
aidandaly24 May 6, 2026
41ec7e9
fix: add maxIterations per phase to prevent context overflow, use for…
aidandaly24 May 6, 2026
362766c
fix: commit-early strategy, unique branch per run, abort on 0-file ex…
aidandaly24 May 6, 2026
35a3e34
fix: add retry on connection errors, fix node 20 symlinks in setup, f…
aidandaly24 May 6, 2026
3bad3e2
fix: constrain reviewer to 10 tool calls max — prevent codebase explo…
aidandaly24 May 6, 2026
6ea6948
fix: pass branch_name to setup prompt — sync branch naming between or…
aidandaly24 May 6, 2026
139a614
feat: parallelize reviewers with ThreadPoolExecutor, relax hard tool …
aidandaly24 May 6, 2026
7026fd9
fix: use origin/main in extract and complete phases — local main ref …
aidandaly24 May 6, 2026
592ed29
feat: add babysit-pr phase — polls for automation reviewer comments, …
aidandaly24 May 7, 2026
9aa0438
feat: add temporary batch runner for parallel bug fixing (easy to rep…
aidandaly24 May 7, 2026
9c0fbb0
feat: add NOT_FIXABLE detection in planner + structured verdict parsi…
aidandaly24 May 7, 2026
69b57d7
feat: babysit phase checks PR reviews (approve/request-changes) not p…
aidandaly24 May 7, 2026
5da1716
feat: batch runner writes per-issue logs and state JSON for dashboard
aidandaly24 May 7, 2026
c8a86c2
fix: write running state immediately, use line-buffered output for pe…
aidandaly24 May 7, 2026
b433d9c
feat: route all output through per-issue file handles for clean paral…
aidandaly24 May 7, 2026
fc5e5c8
feat: babysit phase also monitors CI failures and auto-fixes them
aidandaly24 May 7, 2026
fa27d8d
fix: raise MaxTokensExceededError instead of silently returning trunc…
aidandaly24 May 7, 2026
82b1ebd
feat: retry phases on MaxTokensExceededError instead of proceeding wi…
aidandaly24 May 7, 2026
d5f7bc1
fix: use explicit file handles instead of sys.stdout swap — thread-sa…
aidandaly24 May 7, 2026
99da49d
fix: re-trigger reviewer workflow after babysit push, fix recursion b…
aidandaly24 May 7, 2026
9b71826
feat: tighter prompts — explicit stop conditions, scope constraints, …
aidandaly24 May 7, 2026
d5ff545
feat: planner writes plan to /tmp/plan.md — survives max_tokens, redu…
aidandaly24 May 7, 2026
10e93cf
fix: _check_ci parses plain text gh output (not JSON), detect tab-sep…
aidandaly24 May 7, 2026
86c81f6
feat: inject context into babysitting agents via dashboard, fix CI ch…
aidandaly24 May 7, 2026
a45d4f3
fix: add npm run format to executor steps — prevents CI format failures
aidandaly24 May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/bug-fixer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Bug Fixer Agent

on:
issues:
types: [labeled]
workflow_dispatch:
inputs:
issue_url:
description: 'GitHub issue URL'
required: true
type: string

permissions:
id-token: write
contents: write
pull-requests: write
issues: write

jobs:
fix-bug:
if: github.event.label.name == 'bug' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Determine issue URL
id: issue
env:
INPUT_URL: ${{ inputs.issue_url }}
EVENT_URL: ${{ github.event.issue.html_url }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
echo "url=$INPUT_URL" >> "$GITHUB_OUTPUT"
else
echo "url=$EVENT_URL" >> "$GITHUB_OUTPUT"
fi

- name: Checkout
uses: actions/checkout@v6

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ secrets.HARNESS_AWS_ROLE_ARN }}
aws-region: us-west-2

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'

- name: Setup uv
uses: astral-sh/setup-uv@v7

- name: Run Bug Fixer Agent
working-directory: agents
env:
HARNESS_ARN: ${{ secrets.HARNESS_ARN }}
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
ISSUE_URL: ${{ steps.issue.outputs.url }}
run: uv sync && uv run python -m bug_fixer.main --issue "$ISSUE_URL"
81 changes: 81 additions & 0 deletions .github/workflows/feature-builder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Feature Builder Agent

on:
workflow_dispatch:
inputs:
devex_doc:
description: 'Path to devex doc (relative to repo root)'
required: true
type: string
impl_doc:
description: 'Path to implementation plan (relative to repo root)'
required: true
type: string
feature_name:
description: 'Feature name (used for branch naming)'
required: true
type: string

permissions:
id-token: write
contents: write
pull-requests: write

jobs:
build-feature:
runs-on: ubuntu-latest
steps:
- name: Validate inputs
env:
FEATURE_NAME: ${{ inputs.feature_name }}
DEVEX_DOC: ${{ inputs.devex_doc }}
IMPL_DOC: ${{ inputs.impl_doc }}
run: |
if [[ ! "$FEATURE_NAME" =~ ^[a-zA-Z0-9_-]+$ ]]; then
echo "Error: feature_name must be alphanumeric with hyphens/underscores only"
exit 1
fi
if [[ "$DEVEX_DOC" == *".."* ]] || [[ "$IMPL_DOC" == *".."* ]]; then
echo "Error: doc paths must not contain '..'"
exit 1
fi
if [[ ! "$DEVEX_DOC" == *.md ]]; then
echo "Error: devex_doc must be a .md file"
exit 1
fi
if [[ ! "$IMPL_DOC" == *.md ]]; then
echo "Error: impl_doc must be a .md file"
exit 1
fi

- name: Checkout
uses: actions/checkout@v6

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: ${{ secrets.HARNESS_AWS_ROLE_ARN }}
aws-region: us-west-2

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'

- name: Setup uv
uses: astral-sh/setup-uv@v7

- name: Run Feature Builder Agent
working-directory: agents
env:
HARNESS_ARN: ${{ secrets.HARNESS_ARN }}
GH_TOKEN: ${{ secrets.PAT_TOKEN }}
DEVEX_DOC: ${{ inputs.devex_doc }}
IMPL_DOC: ${{ inputs.impl_doc }}
FEATURE_NAME: ${{ inputs.feature_name }}
run: |
uv sync
uv run python -m feature_builder.main \
--devex "../$DEVEX_DOC" \
--impl "../$IMPL_DOC" \
--name "$FEATURE_NAME"
3 changes: 3 additions & 0 deletions agents/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config.yaml
__pycache__/
.venv/
167 changes: 167 additions & 0 deletions agents/bug_fixer/batch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
"""Batch Bug Fixer — runs multiple issues in parallel.

Usage:
uv run python -m bug_fixer.batch --issues url1 url2 url3
uv run python -m bug_fixer.batch --issues-file issues.txt
uv run python -m bug_fixer.batch --label bug --repo aws/agentcore-cli --max 5

This is a temporary batch runner. The architecture for parallel orchestration
may change — this file is designed to be easy to remove/replace.
"""

import argparse
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path

from orchestrations.fix_and_review.orchestrator import run_pipeline
from orchestrations.fix_and_review.phases.setup import set_prompts_dir

PROMPTS_DIR = Path(__file__).resolve().parent / "prompts"
MAX_PARALLEL = 5


def fetch_issues_by_label(repo: str, label: str, max_count: int) -> list[str]:
import subprocess
result = subprocess.run(
["gh", "issue", "list", "--repo", repo, "--label", label,
"--state", "open", "--limit", str(max_count), "--json", "url", "--jq", ".[].url"],
capture_output=True, text=True,
)
if result.returncode != 0:
print(f"Error fetching issues: {result.stderr}", file=sys.stderr)
return []
return [url.strip() for url in result.stdout.strip().split("\n") if url.strip()]


def run_single_issue(issue_url: str, config_path: str) -> dict:
import json as json_mod

issue_number = issue_url.rstrip("/").split("/")[-1]
log_path = Path(f"/tmp/batch-issue-{issue_number}.log")
start = time.time()

# Mark as running immediately so dashboard can see it
_update_state(issue_number, {
"issue": issue_url,
"number": issue_number,
"status": "running",
"duration": 0,
"log_file": str(log_path),
})

try:
log_file = open(log_path, "w", buffering=1)
set_prompts_dir(PROMPTS_DIR)
exit_code = run_pipeline(
issue_url=issue_url,
config_path=config_path,
prompts_dir=PROMPTS_DIR,
output=log_file,
)
log_file.close()
result = {
"issue": issue_url,
"number": issue_number,
"status": "success" if exit_code == 0 else "failed",
"duration": int(time.time() - start),
"log_file": str(log_path),
}
except Exception as e:
result = {
"issue": issue_url,
"number": issue_number,
"status": "error",
"error": str(e),
"duration": int(time.time() - start),
"log_file": str(log_path),
}

# Write final state
_update_state(issue_number, result)
return result


def _update_state(issue_number: str, result: dict) -> None:
import json as json_mod
import fcntl

state_path = Path("/tmp/batch-state.json")
try:
with open(state_path, "r+") as f:
fcntl.flock(f, fcntl.LOCK_EX)
state = json_mod.load(f)
state[issue_number] = result
f.seek(0)
f.truncate()
json_mod.dump(state, f, indent=2)
fcntl.flock(f, fcntl.LOCK_UN)
except (FileNotFoundError, json_mod.JSONDecodeError):
with open(state_path, "w") as f:
fcntl.flock(f, fcntl.LOCK_EX)
json_mod.dump({issue_number: result}, f, indent=2)
fcntl.flock(f, fcntl.LOCK_UN)


def main():
parser = argparse.ArgumentParser(description="Batch Bug Fixer")
parser.add_argument("--issues", nargs="+", help="GitHub issue URLs")
parser.add_argument("--issues-file", help="File with one issue URL per line")
parser.add_argument("--label", default="bug", help="Label to filter issues by")
parser.add_argument("--repo", default="aws/agentcore-cli", help="Repo to fetch issues from")
parser.add_argument("--max", type=int, default=5, help="Max issues to process")
parser.add_argument("--parallel", type=int, default=MAX_PARALLEL, help="Max parallel workers")
parser.add_argument("--config", default="config.yaml", help="Config YAML path")
args = parser.parse_args()

# Resolve issue list
if args.issues:
issues = args.issues
elif args.issues_file:
issues = [line.strip() for line in Path(args.issues_file).read_text().split("\n") if line.strip()]
else:
print(f"Fetching open issues with label '{args.label}' from {args.repo}...", flush=True)
issues = fetch_issues_by_label(args.repo, args.label, args.max)

if not issues:
print("No issues found.", file=sys.stderr)
return 1

issues = issues[:args.max]
workers = min(args.parallel, len(issues))

print(f"\n=== Batch Bug Fixer ===")
print(f"Issues: {len(issues)}")
print(f"Parallel workers: {workers}")
print(f"Issues:")
for url in issues:
print(f" - {url}")
print()

start = time.time()
results = []

with ThreadPoolExecutor(max_workers=workers) as executor:
futures = {
executor.submit(run_single_issue, url, args.config): url
for url in issues
}
for future in as_completed(futures):
result = future.result()
results.append(result)
status_icon = "✓" if result["status"] == "success" else "✗"
print(f" {status_icon} {result['issue']} — {result['status']} ({result['duration']}s)", flush=True)

total_time = int(time.time() - start)
successes = sum(1 for r in results if r["status"] == "success")
failures = len(results) - successes

print(f"\n=== Batch Complete [{total_time}s] ===")
print(f" {successes}/{len(results)} succeeded, {failures} failed")

return 0 if failures == 0 else 1


if __name__ == "__main__":
sys.exit(main())
40 changes: 40 additions & 0 deletions agents/bug_fixer/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
"""Bug Fixer Agent — resolves GitHub issues labeled 'bug'.

Usage:
uv run bug_fixer/main.py --issue https://github.com/aws/agentcore-cli/issues/123
uv run bug_fixer/main.py --issue https://github.com/aws/agentcore-cli/issues/123 --config config.yaml
"""

import argparse
import sys
from pathlib import Path

from orchestrations.fix_and_review.orchestrator import run_pipeline

PROMPTS_DIR = Path(__file__).resolve().parent / "prompts"


def main():
parser = argparse.ArgumentParser(description="Bug Fixer Agent")
parser.add_argument("--issue", required=True, help="GitHub issue URL")
parser.add_argument("--config", default="config.yaml", help="Config YAML path")
parser.add_argument("--aws-profile", help="Override AWS profile")
parser.add_argument("--harness-arn", help="Override harness ARN")
args = parser.parse_args()

overrides = {}
if args.aws_profile:
overrides["aws_profile"] = args.aws_profile
if args.harness_arn:
overrides["harness_arn"] = args.harness_arn

return run_pipeline(
issue_url=args.issue,
config_path=args.config,
prompts_dir=PROMPTS_DIR,
**overrides,
)


if __name__ == "__main__":
sys.exit(main())
31 changes: 31 additions & 0 deletions agents/bug_fixer/prompts/executor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
You are a senior software engineer implementing a planned bug fix.

The plan:
{plan}

## Steps

1. Make the code changes described in the plan. Only touch the files listed.
2. Run formatter: `npm run format 2>&1 | tail -5`
3. COMMIT IMMEDIATELY: `git add -A && git commit -m "fix: {commit_message}"`
4. Run typecheck: `npm run typecheck 2>&1 | tail -20`
5. If typecheck fails, fix ONLY the errors you caused (not pre-existing ones). Run format again. Commit.
6. Run targeted tests: `npx vitest run --project unit path/to/test.ts 2>&1 | tail -30`
7. If your tests fail, fix, format, and commit.
8. Push: `git push origin {branch_name}`

## STOP CONDITIONS

- STOP after pushing. You are done.
- STOP if typecheck still fails after 2 fix attempts. Commit what you have and push anyway.
- STOP if you've made more than 15 tool calls. Commit whatever state you're in and push.
- DO NOT keep exploring the codebase after making your changes.
- DO NOT refactor, rename, or "improve" code outside the plan.
- DO NOT run `npm run test:unit` (full suite). Only targeted tests.
- DO NOT read files that aren't in the plan unless you need to check a type signature.

## COMMIT STRATEGY

Your FIRST action after writing code should be `git add -A && git commit`.
A commit with typecheck errors is better than no commit at all.
The orchestrator can recover from a commit with errors. It CANNOT recover from no commit.
Loading
Loading