Out Mon Sep 21 · due Sun Oct 4, 11:59pm · all five assignments
You will build madsLoop, a minimal coding-agent harness, and test it with SWE-bench Pro. An agent is a model in a loop. It proposes tool calls, and your harness executes them and feeds the results back. This task aims to build everything you need beyond the model API.
⚠️ Part 0 and Part 1 outline the basics of an agent. DO NOT TRUST IT.
They describe a design choice, not the answer. Follow their overall structure, but critically evaluate the underlying logic. Treat the interface, tools, workflow, and prompts as starting points, test them against real tasks and revise them as needed. The write-up asks you what you did differently and why. An agent that follows everything might fail. The only requirement is that 1) Use qwen3.6-35b-a3b as the model; 2) we can call run_task.sh to generate a patch that can be evaluated (with run_all.sh) and the agent logs; 3) all logs follow the schema required.
⚠️ Not all given tasks are solvable.
We will provide 10 tasks. Some may be impossible to complete even with a perfect harness. However, if a task can be solved by modifying Parts 0 and 1, it is considered solvable.
We use the course proxy at api.cs2680.com, an OpenAI-compatible endpoint, to serve
the requests.
Export your key in the shell you run from:
export CS2680_API_KEY="hyi-..." # your key
import os
from openai import OpenAI
# Read the key from the environment. Never hardcode it and never commit it.
# Your agent runs inside the task container, so you have to pass it
# (e.g. `docker run -e CS2680_API_KEY=...`, see 2.3).
CS2680_API_KEY = os.environ["CS2680_API_KEY"]
client = OpenAI(
base_url="https://api.cs2680.com/v1",
api_key=CS2680_API_KEY,
)
resp = client.chat.completions.create(
model="qwen3.6-35b-a3b",
messages=[{"role": "user", "content": "hello"}],
tools=[...],
)
In this assignment, qwen3.6-35b-a3b will serve as the backbone model and should be used for all evaluations.
The agent you designed will runs inside Docker containers: each task ships as a prebuilt image with the
repository and its dependencies, your agent runs inside that container, and the official evaluation runs in
another one too. Make sure Docker is installed and that you can run it without sudo; the ten provided task
images are about 1.6–1.8 GB each.
linux/amd64 and run natively.PLATFORM_FLAG="--platform linux/amd64" in the shell you run the scripts from. run_task.sh passes it to docker pull and docker run. Expect slower runs.If you encounter difficulty setting up the environment, please reach out to the TFs.
python madsLoop.py -p "<problem statement>" [--log] <workdir>
-p — the issue text: the problem_statement field of the task's entry in
evaluation_scripts/agent_task_input.json. This is the task description your agent receives.<workdir> — path to the repo checkout.
Your agent may read, search, edit, and run commands only inside this directory. We will use /app as an example.--log — trace logging, off by default. When set, your agent writes its run
trace into a madsLoop_logs/ directory in the current working directory (create
it if it doesn't exist), see schema in 0.3. We pass --log when we evaluate your agent.<workdir>, and later patches will
be generated by git diff.developer_logs/)Your submission includes a developer_logs/ directory documenting how you built
madsLoop with a coding agent. The logs should be in .jsonl format. If you use a coding agent through a terminal or IDE, you can typically find the session logs in the following locations:
| Tool | How to export |
|---|---|
| Claude Code (terminal, or the VS Code / JetBrains extension) | /export in the session, or the session transcripts under ~/.claude/projects/<project>/*.jsonl |
| OpenAI Codex (CLI or IDE extension) | session logs under ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl |
| Cursor | ~/.cursor/projects/<project>/agent-transcripts/*.jsonl |
| Gemini CLI | ~/.gemini/tmp/<project_hash>/chats/session-*.jsonl |
madsLoop_logs/)When run with --log, your agent should write its trace into ./madsLoop_logs/ as
JSONL (one file per run, e.g. madsLoop_logs/run1.jsonl): one JSON object per line,
one line per event, in order, from which the whole run can be reconstructed. Every
line carries timestamp (ISO-8601) and event; the remaining fields depend on the
event type:
event |
required fields |
|---|---|
run_start |
model_id, workdir |
api_request |
iteration, prompt_tokens, completion_tokens, total_tokens (straight from response.usage) |
api_retry |
iteration, error, backoff_s |
tool_call |
iteration, tool_name, arguments |
tool_result |
iteration, tool_name, result (truncated exactly as fed to the model), is_error |
run_end |
reason (done / no_tool_calls / error / a self-imposed limit), num_iterations |
Example lines:
{"timestamp": "2026-09-12T14:03:07+00:00", "event": "run_start", "model_id": "qwen3.6-35b-a3b", "workdir": "/app"}
{"timestamp": "2026-09-12T14:03:11+00:00", "event": "api_request", "iteration": 1, "prompt_tokens": 1834, "completion_tokens": 96, "total_tokens": 1930}
{"timestamp": "2026-09-12T14:03:11+00:00", "event": "tool_call", "iteration": 1, "tool_name": "bash", "arguments": {"command": "grep -rn 'version_check' qutebrowser/utils/"}}
{"timestamp": "2026-09-12T14:03:12+00:00", "event": "tool_result", "iteration": 1, "tool_name": "bash", "result": "qutebrowser/utils/qtutils.py:91: ...", "is_error": false}
{"timestamp": "2026-09-12T14:12:40+00:00", "event": "run_end", "reason": "done", "num_iterations": 14}
Please stick to exactly these fields. During development, you may use stderr for print-based debugging as needed. However, make sure that the output follows this schema when using --log.
This is a list you tell the model which tools are available to use: each has a name, a natural-language description, and a JSON Schema for its parameters.
A workable minimal tool set, and you may design a different one:
| Tool | Purpose |
|---|---|
bash(command) |
run a shell command in <workdir> (search, run tests, inspect) |
read_file(path) |
return file contents (with line numbers helps the model edit) |
edit_file(path, old_string, new_string) |
replace an exact string in a file |
done(summary) |
agent declares it has finished |
The while-loop that makes it an agent rather than a chatbot:
messages = [system prompt, user message containing the problem statement]
loop:
response = model(messages, tools)
if response contains tool calls:
execute each requested tool
append the assistant message AND one tool-result message per call
continue
else:
break # model produced a final answer
It is important to fix failures you introduced and pre-existing failures unrelated to your change can be ignored . You can compare against the untouched code if unsure.
The model is stateless: every request must resend the entire history. Your harness owns that history:
messages array: system prompt, initial user message, then alternating
assistant turns (including their tool calls) and tool-result messages. Every tool
call the model makes must be paired with exactly one result message referencing its
call ID — an unpaired call is an API error./tmp from
the requirements and register each with repro_check;That's the whole minimal harness: tool definitions (what the model can ask for), the loop + tool executors (your code doing what it asked), and conversation state (the memory tying the turns together).
⚠️ Part 2 can be trusted. :>
run_task.sh can be modified. Make sure the run_task.sh can work with the changes you made for Part 0 and 1.
Each task entry in agent_task_input.json specifies a prebuilt Docker image (docker_image) containing the target repository. The repository is checked out at the designated base_commit in /app, with all required dependencies preinstalled. To run your agent, first launch the corresponding container, then execute the agent inside the running container using /app as the working repository.
The image names for the other tasks are in the docker_image field of their
evaluation_scripts/agent_task_input.json entries.
On an x86-64 host, the image runs natively:
# the first task's image (jefzda/sweap-images:qutebrowser.qutebrowser-…)
IMG=$(python -c "import json; print(list(json.load(open('evaluation_scripts/agent_task_input.json')).values())[0]['docker_image'])")
docker pull "$IMG"
docker_env.shThe task images don't have the openai package installed. Your repo must include a
docker_env.sh at the root that installs whatever your agent needs. It will run inside
the container before the agent runs. The minimal version:
#!/bin/bash
# docker_env.sh — container setup, runs before madsLoop.py
pip install -q openai
If your agent needs anything else (e.g. pinning an openai version), add it in docker_env.sh
to modify the container environment. Make sure your
agent run on Python 3.10+ with openai and the standard library.
# the first task: its instance_id, and its problem statement (the -p payload) as a file
IID=$(python -c "import json; print(list(json.load(open('evaluation_scripts/agent_task_input.json')))[0])")
python -c "import json; print(list(json.load(open('evaluation_scripts/agent_task_input.json')).values())[0]['problem_statement'])" > problem.txt
docker run --rm \
--entrypoint bash \
-v "$PWD:/madsLoop" \
-e CS2680_API_KEY="$CS2680_API_KEY" \
"$IMG" \
-c 'bash /madsLoop/docker_env.sh && \
cd /tmp && \
python /madsLoop/madsLoop.py -p "$(cat /madsLoop/problem.txt)" --log /app && \
cp -r madsLoop_logs /madsLoop/ && \
git -C /app diff' \
> "model_patch_$IID.diff"
The script above identifies the ID and problem statement for each task, mounts the corresponding codebase in a container, passes your CS2680_API_KEY through to it, and sets up the environment. It then runs madsLoop. After the agent finishes modifying the repository, the script exports the madsLoop logs and generates a diff patch. Feel free to modify the run_task.sh, just make sure run_all.sh can call it.
Evaluate it for real with the official SWE-bench Pro evaluation script
(scaleapi/SWE-bench_Pro-os), the same
tool we grade with. It applies your diff to a fresh checkout, restores the held-out
test files, and runs the actual tests. Bundle your patches into a patches.json as in make_patches.py:
python - <<'EOF'
import json, pathlib
tasks = json.load(open("evaluation_scripts/agent_task_input.json"))
predictions = [{"instance_id": iid,
"model_patch": pathlib.Path(f"model_patch_{iid}.diff").read_text(),
"prefix": "madsLoop"}
for iid in tasks]
json.dump(predictions, open("patches.json", "w"), indent=2)
EOF
and run the evaluation:
bash evaluation_scripts/evaluate.sh
It writes pro_eval/eval_results.json, mapping each instance_id to true/false
(resolved or not), plus per-instance test logs under pro_eval/<instance_id>/ —
that's your ground truth for whether a task is actually solved, and can be used to iterate on your agent . Patches are graded by applying them to a fresh checkout, restoring the held-out test
files, and running hidden tests — any edits your agent made to existing test files are
overwritten, so editing tests cannot help you.
evaluation_scripts/)We provide an evaluation_scripts/ folder as the evaluation entry point. Copy the folder
into your repo root as-is. It contains:
| File | What it does |
|---|---|
agent_task_input.json |
the local-test tasks, keyed by instance_id — the only task data your agent may use |
task_test.json |
per-task evaluation data (test setup, graded tests), keyed by instance_id — used by evaluate.sh only; your agent must never read it |
run_task.sh <idx> |
pulls the task image and runs your agent in its container (2.1–2.3); writes model_patch_<instance_id>.diff and copies madsLoop_logs/ back |
make_patches.py |
bundles your model_patch_*.diff files into patches.json |
evaluate.sh |
runs the official SWE-bench Pro evaluation (2.4) on patches.json, joining agent_task_input.json with task_test.json for the test setup |
Run them from your repo root (the directory containing madsLoop.py); if you keep the
folder elsewhere, set MADSLOOP_REPO to your repo root. The task JSONs are
always read from inside evaluation_scripts/ itself, and all outputs (patches,
patches.json, madsLoop_logs/, pro_eval/) land in the repo root:
bash evaluation_scripts/run_task.sh 0 # one task, or run_all.sh for all four
python3 evaluation_scripts/make_patches.py
bash evaluation_scripts/evaluate.sh # -> pro_eval/eval_results.json
What you may change in evaluation_scripts/. You are free to edit
run_task.sh, agent_task_input.json and task_test.json to test things
locally — point them at your own tasks, add debugging, adjust how the container
is set up. run_all.sh, evaluate.sh and make_patches.py are what we keep: we run our own
copies of them unchanged.
mytest/)Beyond the tasks we give you, you build four tasks of your own. Do not take them from SWE-bench Pro, or from any other agent benchmark. The point is to see whether your harness holds up on a task nobody has tuned it for. Source each of the four from one of:
Building a task means producing three things.
1. One paragraph of description. This is everything your agent is told. It takes
the place of problem_statement, requirements and interface in the tasks we gave
you, and it should be a single paragraph, in the problem_statement field. Say what
is wrong or missing, how it shows up, and what counts as done. Keep requirements and interface as keys
with "" values, so the entry has the same shape as agent_task_input.json and your
agent reads it unchanged.
2. Tests that show it is fixed. Record them in mytest/task_test.json, keyed by
instance_id, with the same four fields as evaluation_scripts/task_test.json:
before_repo_set_cmd, selected_test_files_to_run, fail_to_pass, pass_to_pass.
fail_to_pass is at least one test that fails at base_commit and passes once the
task is done. pass_to_pass is tests that pass in both states, so that a patch which
breaks the repo cannot be scored as a fix.
3. A container. mytest/tasks/<instance_id>/Dockerfile builds an image with the
repo checked out at base_commit and every dependency already installed, so the tests
run without network access. Tag it, and put that tag in the task's docker_image
field — that is the field run_task.sh runs. You do not have to push it anywhere:
when the pull fails, run_task.sh falls back to the image of that name in your
local Docker daemon.
Alongside the Dockerfile, include mytest/tasks/<instance_id>/gold.diff: the real fix,
the upstream commit or whatever you wrote at the time. It is there so the tests can be trusted, meaning applying it must turn every
fail_to_pass test green.
Split the four tasks into two files:
mytest/solved_after_change.json — two tasks your agent that solves the given tasks could not solve at
first, and can solve after you changed something in the agent. The change must be in the
submitted code. The write-up and the video explain it.mytest/unsolved.json — two tasks your agent still cannot solve when you submit.
Their gold.diff must still make their tests pass.Running them. The workflow is the one from 2.1–2.4, with one substitution: the
official evaluation in evaluate.sh only knows SWE-bench Pro instances, so it cannot
score your images. Write a mytest/verify.sh to do that job instead.
IID=... # one of your four instance_ids
IMG=... # that task's docker_image tag
docker build -t "$IMG" "mytest/tasks/$IID/" # 1. build the image
TASKS_JSON=mytest/solved_after_change.json \
bash evaluation_scripts/run_task.sh 0 # 2. agent -> model_patch_$IID.diff
bash mytest/verify.sh "$IID" "model_patch_$IID.diff" # 3. apply, run tests, report
verify.sh takes an instance_id and a diff, starts a fresh container from that task's
docker_image, resets the checkout with before_repo_set_cmd, applies the diff, runs
selected_test_files_to_run, and prints RESOLVED only if every fail_to_pass
test passes and no pass_to_pass test regresses, the same rule the official
evaluation applies. Run it with no diff to get the base_commit baseline.
Save its output under mytest/results/ for each of the four tasks:
<instance_id>.before.log (at base_commit, showing fail_to_pass failing) and
<instance_id>.after.log (with your agent's patch for the two in
solved_after_change.json; with gold.diff for the two in unsolved.json).
Minimal handin under mytest/:
| Path | Count | What it is |
|---|---|---|
solved_after_change.json |
2 entries | agent-side task fields, same shape as agent_task_input.json |
unsolved.json |
2 entries | same |
task_test.json |
4 entries | before_repo_set_cmd, selected_test_files_to_run, fail_to_pass, pass_to_pass |
tasks/<instance_id>/Dockerfile |
4 | builds the image named in that task's docker_image |
tasks/<instance_id>/gold.diff |
4 | the real fix; turns fail_to_pass green |
verify.sh |
1 | applies a diff in the container and reports RESOLVED / unresolved |
results/<instance_id>.{before,after}.log |
8 | the runs you recorded |
The submission has three parts: the code (a GitHub repo), a write-up, and a
video. We will use the same private GitHub repository as in Assignment 1 with code and the writeup under assignment2/.
The video should be uploaded to Canvas.
We grade by cloning
your repo and invoking your agent automatically, so the layout below is a hard
requirement — if madsLoop.py is not at the repo root, our runner cannot call it and
the submission scores zero.
Required repository layout:
<assignment2>
├── madsLoop.py # the entry point, at the ROOT
├── docker_env.sh # container setup (see 2.2), at the ROOT
├── writeup.pdf # write up pdf
├── src/ # the modules madsLoop.py imports, the harness design
│ ├── agentic_loop.py # (any structure you like — this is one example)
│ ├── tools.py
│ └── prompts.py
├── developer_logs/ # how you built madsLoop (see 0.2)
│ └── claude_code_session.jsonl # logs in jsonl
├── evaluation_scripts/ # provided by us (see 2.5)
│ ├── agent_task_input.json #
│ ├── task_test.json #
│ ├── run_task.sh #
│ ├── run_all.sh # entry point for our grading evaluation, do not change it
│ ├── make_patches.py #
│ └── evaluate.sh #
├── mytest/ # four tasks you build yourself (see 2.6)
│ ├── solved_after_change.json # 2 tasks: unsolved at first, solved after a change to your agent
│ ├── unsolved.json # 2 tasks: still unsolved at the end
│ ├── task_test.json #
│ ├── tasks/<instance_id>/Dockerfile # one per task: builds the image named in docker_image
│ ├── tasks/<instance_id>/gold.diff # one per task: the real fix, never shown to your agent
│ ├── verify.sh # applies a patch in the container and runs the tests
│ └── results/ # <instance_id>.before.log and .after.log per task
└── README.md # optional: design notes, tool-set rationale
We evaluate by running run_all.sh, which invokes run_task.sh for each task.
Your run_task.sh is expected to invoke your agent with --log, and to produce a
patch for each task; run_all.sh then bundles those patches and evaluates them.
A one-page (hard limit) document in PDF in the repo with three sections:
agent_task_input.json you think (not the agent thinks) are unsolvable, and why. Name
the instance_ids and give the evidence from your runs: what the agent tried, where
it got stuck, and what about the task (not your agent) makes it unsolvable. You can change the instructions in Parts 0 and 1 in any way you think is appropriate. Failures caused by those instructions should not be considered unsolvable. Instead, identify problems with the tasks themselves that make them impossible to solve.solved_after_change.json tasks pass. For each of the
two: what failed at first, what you changed in the agent, and how
that change turns the failure into a pass.The document must be entirely your own work. No AI-generated text.
Record a 3-minute video. First walk through your code: the entry point, the tools,
the agentic loop, and how conversation state is kept. Then show which part of the code
you changed to turn the two solved_after_change.json tasks from unsolved to
solved, and why that change made the difference. It should be uploaded to Canvas.
| Part | Weight | What is graded |
|---|---|---|
| Hidden tasks | 40% | We run your agent with run_task.sh on the provided tasks and some more hidden SWE-bench Pro tasks (same format as agent_task_input.json) and grade each patch with the official evaluation. Score is the fraction resolved among the solvable tasks. Get a PASS for unsolvable tasks will lose points. All hidden tasks are solvable. Use your own judgment to decide which tasks are solvable, and then solve them. |
| Write-up | 40% | 20% — which given tasks are unsolvable and why. 10% — what you did differently from the handout. 10% — the mytest/ tasks: four tasks you built yourself, none of them from SWE-bench Pro, each with a single-paragraph problem_statement, a Dockerfile that builds, and fail_to_pass / pass_to_pass tests that gold.diff turns green; all four run under run_task.sh and score under your verify.sh, the two solved_after_change.json tasks fail before and pass after the change you describe, and the explanation of the change matches the code. |
| Video | 20% | The 3-minute video: a clear walk-through of the code, and a correct account of which change turned the two solved_after_change.json tasks from unsolved to solved. |
Have fun with your agent! The starter code can be found here, under the folder assignment2.