Skip to content

Commit d4aa2c7

Browse files
Copilotmrjf
andauthored
Merge origin/main: resolve all conflicts, keep main's CI-validated features + our unique additions
Agent-Logs-Url: https://github.com/githubnext/tsessebe/sessions/2bde55cc-93a8-4557-9554-af48ffc22e0f Co-authored-by: mrjf <180956+mrjf@users.noreply.github.com>
1 parent 6d589ad commit d4aa2c7

303 files changed

Lines changed: 59365 additions & 9333 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
---
2+
schedule: every 6h
3+
---
4+
5+
# Performance Comparison: tsb (TypeScript) vs pandas (Python)
6+
7+
## Goal
8+
9+
Systematically benchmark every tsb function against its pandas equivalent, one function per iteration. Each iteration picks a function that has not yet been benchmarked, writes a matching performance test for both tsb (TypeScript/Bun) and pandas (Python), runs both, and records the timing results. The benchmark results are displayed on the playground pages doc site.
10+
11+
This is an open-ended program — it runs continuously, always adding the next benchmark comparison.
12+
13+
### How each iteration works
14+
15+
1. **Read existing benchmarks** — check `benchmarks/tsb/` and `benchmarks/pandas/` to see which functions are already benchmarked.
16+
2. **Pick ONE function** from `src/` that has no benchmark yet. Prioritize core operations (Series, DataFrame, GroupBy, etc.).
17+
3. **Write a TypeScript benchmark** in `benchmarks/tsb/bench_{function}.ts` that:
18+
- Creates a realistic dataset (e.g. 100,000 rows)
19+
- Runs the operation in a tight loop (warm-up + measured iterations)
20+
- Outputs JSON: `{"function": "...", "mean_ms": ..., "iterations": ..., "total_ms": ...}`
21+
4. **Write a matching Python benchmark** in `benchmarks/pandas/bench_{function}.py` that:
22+
- Creates the same dataset as the TypeScript version
23+
- Runs the same operation with the same loop structure
24+
- Outputs the same JSON format
25+
5. **Run both benchmarks** via `benchmarks/run_benchmarks.sh` and capture results.
26+
6. **Update `benchmarks/results.json`** with the new timing data.
27+
7. **Update `playground/benchmarks.html`** to display the new function's comparison metrics.
28+
29+
### Key constraints
30+
31+
- **Matching datasets** — both benchmarks must use identical data (same size, same values where possible).
32+
- **Fair comparison** — same number of warm-up and measured iterations for both.
33+
- **JSON output** — every benchmark script must output a single JSON line to stdout.
34+
- **No modifications to `src/`** — benchmark code is separate from library code.
35+
- **Python environment** — install pandas via pip if not present.
36+
37+
## Target
38+
39+
Only modify these files:
40+
- `benchmarks/**` — benchmark scripts and results
41+
- `playground/benchmarks.html` — performance comparison playground page
42+
- `playground/index.html` — add/update link to benchmarks page
43+
44+
Do NOT modify:
45+
- `src/**` — library source code
46+
- `tests/**` — test files
47+
- `README.md` — read-only
48+
- `.autoloop/programs/**` — program definitions (except this file's code/ dir)
49+
- `.github/workflows/autoloop*` — autoloop workflow files
50+
51+
## Evaluation
52+
53+
```bash
54+
# Set up Python environment if needed
55+
if ! command -v python3 &>/dev/null; then
56+
echo "Python3 not found, skipping"
57+
fi
58+
pip3 install pandas --quiet 2>/dev/null || true
59+
60+
# Count the number of benchmark pairs (functions with both TS and Python benchmarks)
61+
ts_benchmarks=$(ls benchmarks/tsb/bench_*.ts 2>/dev/null | wc -l | tr -d ' ')
62+
py_benchmarks=$(ls benchmarks/pandas/bench_*.py 2>/dev/null | wc -l | tr -d ' ')
63+
64+
# The metric is the minimum of the two (both must exist for a complete benchmark)
65+
if [ "$ts_benchmarks" -lt "$py_benchmarks" ]; then
66+
count=$ts_benchmarks
67+
else
68+
count=$py_benchmarks
69+
fi
70+
71+
echo "{\"benchmarked_functions\": ${count:-0}}"
72+
```
73+
74+
The metric is `benchmarked_functions`. **Higher is better.**

.github/workflows/autoloop.lock.yml

Lines changed: 20 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/autoloop.md

Lines changed: 111 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ safe-outputs:
4545
title-prefix: "[Autoloop] "
4646
labels: [automation, autoloop]
4747
protected-files: fallback-to-issue
48+
preserve-branch-name: true
4849
max: 1
4950
push-to-pull-request-branch:
5051
target: "*"
@@ -431,11 +432,98 @@ steps:
431432
if selected in issue_programs:
432433
selected_issue = issue_programs[selected]["issue_number"]
433434
435+
# Look up existing PR for the selected program's canonical branch
436+
existing_pr = None
437+
head_branch = None
438+
439+
def verify_pr_is_open(pr_number):
440+
"""Check if a PR is still open via the GitHub API. Returns True if open."""
441+
try:
442+
verify_url = f"https://api.github.com/repos/{repo}/pulls/{pr_number}"
443+
verify_req = urllib.request.Request(verify_url, headers={
444+
"Authorization": f"token {github_token}",
445+
"Accept": "application/vnd.github.v3+json",
446+
})
447+
with urllib.request.urlopen(verify_req, timeout=30) as verify_resp:
448+
pr_data = json.loads(verify_resp.read().decode())
449+
return pr_data.get("state") == "open"
450+
except Exception:
451+
return True # If we can't verify, assume it's open (best effort)
452+
453+
if selected:
454+
head_branch = f"autoloop/{selected}"
455+
owner = repo.split("/")[0] if "/" in repo else ""
456+
if owner:
457+
# Strategy 1: exact branch match (works when branch has no framework suffix)
458+
try:
459+
pr_api_url = (
460+
f"https://api.github.com/repos/{repo}/pulls"
461+
f"?state=open&head={owner}:{head_branch}&per_page=5"
462+
)
463+
pr_req = urllib.request.Request(pr_api_url, headers={
464+
"Authorization": f"token {github_token}",
465+
"Accept": "application/vnd.github.v3+json",
466+
})
467+
with urllib.request.urlopen(pr_req, timeout=30) as pr_resp:
468+
open_prs = json.loads(pr_resp.read().decode())
469+
if open_prs:
470+
existing_pr = open_prs[0]["number"]
471+
print(f" Found existing PR #{existing_pr} for exact branch {head_branch}")
472+
except Exception as e:
473+
print(f" Warning: could not check for existing PRs by exact branch: {e}")
474+
475+
# Strategy 2: search by title and branch prefix (catches framework-generated
476+
# hash suffixes like autoloop/name-a1b2c3d4e5f6g7h8 created by create-pull-request)
477+
if existing_pr is None:
478+
try:
479+
title_marker = f"[Autoloop: {selected}]"
480+
branch_prefix = head_branch # e.g. autoloop/perf-comparison
481+
list_url = (
482+
f"https://api.github.com/repos/{repo}/pulls"
483+
f"?state=open&per_page=100&sort=created&direction=desc"
484+
)
485+
list_req = urllib.request.Request(list_url, headers={
486+
"Authorization": f"token {github_token}",
487+
"Accept": "application/vnd.github.v3+json",
488+
})
489+
with urllib.request.urlopen(list_req, timeout=30) as list_resp:
490+
all_open_prs = json.loads(list_resp.read().decode())
491+
# Match branch names: exact canonical name or canonical + framework hash suffix
492+
branch_pattern = re.compile(r'^' + re.escape(branch_prefix) + r'(-[0-9a-f]{16})?$')
493+
for pr in all_open_prs:
494+
pr_title = pr.get("title", "")
495+
pr_head_ref = pr.get("head", {}).get("ref", "")
496+
if title_marker in pr_title or branch_pattern.match(pr_head_ref):
497+
existing_pr = pr["number"]
498+
print(f" Found existing PR #{existing_pr} by title/branch-prefix (branch: {pr_head_ref})")
499+
break
500+
if existing_pr is None:
501+
print(f" No existing PR found for program {selected}")
502+
except Exception as e:
503+
print(f" Warning: could not search for existing PRs by title/prefix: {e}")
504+
else:
505+
print(f" Warning: could not parse owner from GITHUB_REPOSITORY='{repo}'")
506+
507+
# Strategy 3: check the state file for a recorded PR number as fallback
508+
if existing_pr is None:
509+
state = read_program_state(selected)
510+
pr_field = state.get("pr") or ""
511+
pr_match = re.match(r'^#?(\d+)$', pr_field.strip())
512+
if pr_match:
513+
pr_num = int(pr_match.group(1))
514+
if verify_pr_is_open(pr_num):
515+
existing_pr = pr_num
516+
print(f" Found open PR #{existing_pr} from state file for {selected}")
517+
else:
518+
print(f" PR #{pr_num} from state file is no longer open — ignoring")
519+
434520
result = {
435521
"selected": selected,
436522
"selected_file": selected_file,
437523
"selected_issue": selected_issue,
438524
"selected_target_metric": selected_target_metric,
525+
"existing_pr": existing_pr,
526+
"head_branch": head_branch,
439527
"issue_programs": {name: info["issue_number"] for name, info in issue_programs.items()},
440528
"deferred": deferred,
441529
"skipped": skipped,
@@ -449,6 +537,10 @@ steps:
449537
450538
print("=== Autoloop Program Check ===")
451539
print(f"Selected program: {selected or '(none)'} ({selected_file or 'n/a'})")
540+
if existing_pr:
541+
print(f"Existing PR: #{existing_pr} (branch: {head_branch})")
542+
else:
543+
print(f"Existing PR: (none — will create on first accepted iteration)")
452544
print(f"Deferred (next run): {deferred or '(none)'}")
453545
print(f"Programs skipped: {[s['name'] for s in skipped] or '(none)'}")
454546
print(f"Programs unconfigured: {unconfigured or '(none)'}")
@@ -538,6 +630,8 @@ The pre-step has already determined which program to run. Read `/tmp/gh-aw/autol
538630
- **`selected_file`**: The full path to the program's markdown file (either `.autoloop/programs/<name>/program.md`, `.autoloop/programs/<name>.md`, or `/tmp/gh-aw/issue-programs/<name>.md` for issue-based programs).
539631
- **`selected_issue`**: The GitHub issue number if the selected program came from an issue, or `null` if it came from a file.
540632
- **`selected_target_metric`**: The `target-metric` value from the program's frontmatter (a number), or `null` if the program is open-ended. Used to check the [halting condition](#halting-condition) after each accepted iteration.
633+
- **`existing_pr`**: The PR number (e.g., `42`) of an already-open PR for this program's branch, or `null` if no open PR exists. **If this is not null, you MUST use `push-to-pull-request-branch` to push to this PR — do NOT call `create-pull-request`.**
634+
- **`head_branch`**: The canonical branch name for this program (e.g., `autoloop/coverage`). Always use this exact branch name — never append suffixes.
541635
- **`issue_programs`**: A mapping of program name → issue number for all discovered issue-based programs.
542636
- **`deferred`**: Other programs that were due but will be handled in future runs.
543637
- **`unconfigured`**: Programs that still have the sentinel or placeholder content.
@@ -550,6 +644,7 @@ If `selected` is not null:
550644
3. Read the current state of all target files.
551645
4. Read the state file `{selected}.md` from the repo-memory folder for all state: the ⚙️ Machine State table (scheduling fields) plus the research sections (priorities, lessons, foreclosed avenues, iteration history).
552646
5. If `selected_issue` is not null, this is an issue-based program — also read the issue comments for any human steering input.
647+
6. **Check `existing_pr`**: if it is not null, a PR already exists — use `push-to-pull-request-branch` to push commits to it. Only call `create-pull-request` when `existing_pr` is null.
553648

554649
## Multiple Programs
555650

@@ -694,7 +789,7 @@ Each run executes **one iteration for the single selected program**:
694789

695790
If the state file does not yet exist, create it in the repo-memory folder using the template defined in the [Repo Memory](#repo-memory) section.
696791

697-
3. Note the `PR` field from the Machine State table. If it contains a PR number (e.g., `#42`), that is the **existing draft PR** for this program — you must update it, not create a new one.
792+
3. Note the `existing_pr` field from `/tmp/gh-aw/autoloop.json`. If it is not null, that is the **existing draft PR** for this program — you must push to it using `push-to-pull-request-branch`, not create a new one. Also check the `PR` field from the Machine State table as a fallback.
698793

699794
### Step 2: Analyze and Propose
700795

@@ -743,15 +838,15 @@ Each run executes **one iteration for the single selected program**:
743838
- Commit message body (after a blank line): `Run: {run_url}` referencing the GitHub Actions run URL.
744839
2. Push the commit to the long-running branch `autoloop/{program-name}`.
745840
3. **Find the existing PR or create one** — follow these steps in order:
746-
a. Check the `PR` field in the state file's **⚙️ Machine State** table. If it contains a PR number (e.g., `#42`), that is the existing draft PR.
747-
b. If the state file has no PR number, search GitHub for open PRs with head branch `autoloop/{program-name}`. Use the GitHub API: `GET /repos/{owner}/{repo}/pulls?state=open&head={owner}:autoloop/{program-name}`.
748-
c. **If an existing PR is found** (from either step a or b): use `push-to-pull-request-branch` to push additional commits to the existing PR. Update the PR body with the latest metric and a summary of the most recent accepted iteration. Add a comment to the PR summarizing the iteration: what changed, old metric, new metric, improvement delta, and a link to the actions run. **Do NOT call `create-pull-request`.**
749-
d. **If NO PR exists** for `autoloop/{program-name}`: create one using `create-pull-request`:
841+
a. **First, check `existing_pr` from `/tmp/gh-aw/autoloop.json`.** The pre-step has already looked up the open PR for this program. If `existing_pr` is not null, that is the existing draft PR — skip to step (c).
842+
b. If `existing_pr` is null, also check the `PR` field in the state file's **⚙️ Machine State** table as a fallback. If it contains a PR number (e.g., `#42`), verify it is still open via the GitHub API.
843+
c. **If an existing PR is found** (from step a or b): use `push-to-pull-request-branch` to push additional commits to the existing PR. Update the PR body with the latest metric and a summary of the most recent accepted iteration. Add a comment to the PR summarizing the iteration: what changed, old metric, new metric, improvement delta, and a link to the actions run. **Do NOT call `create-pull-request`.**
844+
d. **If NO PR exists** for `autoloop/{program-name}` (both `existing_pr` is null AND the state file has no PR): create one using `create-pull-request`:
750845
- Branch: `autoloop/{program-name}` (the branch you already created in Step 3 — do NOT let the framework auto-generate a branch name)
751846
- Title: `[Autoloop: {program-name}]`
752847
- Body includes: a summary of the program goal, link to the steering issue, the current best metric, and AI disclosure: `🤖 *This PR is maintained by Autoloop. Each accepted iteration adds a commit to this branch.*`
753848

754-
> ⚠️ **Never create a new PR if one already exists for `autoloop/{program-name}`.** Each program must have exactly one draft PR at any time. If you are unsure whether a PR exists, check the GitHub API before calling `create-pull-request`.
849+
> ⚠️ **Never create a new PR if one already exists for `autoloop/{program-name}`.** Each program must have exactly one draft PR at any time. The pre-step provides `existing_pr` in autoloop.json — always check it first. Only call `create-pull-request` when `existing_pr` is null AND the state file has no PR number.
755850
4. Ensure the steering issue exists (see [Steering Issue](#steering-issue) below). Add a comment to the steering issue linking to the commit and actions run.
756851
5. Add an entry to the experiment log issue.
757852
6. Update the state file `{program-name}.md` in the repo-memory folder:
@@ -790,6 +885,13 @@ Maintain a single open issue **per program** titled `[Autoloop: {program-name}]
790885
```markdown
791886
🤖 *Autoloop — an iterative optimization agent for this repository.*
792887

888+
| | |
889+
|---|---|
890+
| **Branch** | [`autoloop/{program-name}`](https://github.com/{owner}/{repo}/tree/autoloop/{program-name}) |
891+
| **Pull Request** | #{pr_number} |
892+
| **Steering Issue** | #{steering_issue_number} |
893+
| **State File** | [`{program-name}.md`](https://github.com/{owner}/{repo}/blob/memory/autoloop/{program-name}.md) |
894+
793895
## Program
794896

795897
**Goal**: {one-line summary from program.md}
@@ -817,6 +919,7 @@ Maintain a single open issue **per program** titled `[Autoloop: {program-name}]
817919
- Iterations in **reverse chronological order** (newest first).
818920
- Each iteration heading links to its GitHub Actions run.
819921
- Use `${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}` for the current run URL.
922+
- The **links table at the top** must always show the current branch, PR, steering issue, and state file. Update the PR number when a new PR is created. When creating a continuation issue for a new month, copy the links table from the previous issue.
820923
- Close the previous month's issue and create a new one at month boundaries.
821924
- Maximum 50 iterations per issue; create a continuation issue if exceeded.
822925

@@ -1148,9 +1251,10 @@ After each iteration, prepend an entry to the **📊 Iteration History** section
11481251
> **Do NOT create a new branch with a suffix for each iteration.**
11491252
> Correct: `autoloop/coverage`
11501253
> Wrong: `autoloop/coverage-abc123`, `autoloop/coverage-iter42`, `autoloop/coverage-deadbeef1234`
1254+
> Use the `head_branch` field from `autoloop.json` — it is always the canonical name.
11511255
11521256
> **Do NOT create a new PR if one already exists for `autoloop/{program-name}`.**
1153-
> Always check the state file's `PR` field and the GitHub API before calling `create-pull-request`. If a PR exists, use `push-to-pull-request-branch` instead.
1257+
> The pre-step provides `existing_pr` in `autoloop.json`. If it is not null, **always** use `push-to-pull-request-branch` — never call `create-pull-request`. Only create a PR when `existing_pr` is null AND the state file has no PR number.
11541258
11551259
> **Do NOT let the gh-aw framework auto-generate a branch name when creating a PR.**
11561260
> Always specify the branch explicitly as `autoloop/{program-name}` when calling `create-pull-request`.

0 commit comments

Comments
 (0)