Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
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 submissions/26512076/week-02/REPORT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
\# Week 02 — Harness A/B Report



\## 1. Variant definition



이번 실험에서는 모델, 태스크, 도구를 동일하게 유지하고 하네스만 변경했다. Provider는 OpenRouter, 모델은 `openrouter/free`를 사용했다. 공통 도구는 `read\_file(path)`와 `count\_pattern(path, pattern)`이다. 태스크는 `app.log`에서 ERROR가 가장 많이 발생한 시간대를 찾는 것이며, 정답 문자열이 최종 답변에 포함되면 성공으로 판정했다.



ReAct 하네스는 매 단계에서 Thought → Action → Observation을 반복하며 전체 대화 기록을 다음 호출에 전달한다. 모델이 답변을 완료하거나 최대 8단계에 도달하면 종료한다. 도구 오류도 Observation으로 돌려주므로 다음 단계에서 행동을 수정할 수 있다.



Plan-then-Execute 하네스는 먼저 전체 계획을 JSON 리스트로 생성한 다음 각 단계를 순서대로 실행한다. 한 단계당 도구 호출은 최대 3라운드이며, 계획에서 벗어났을 때 재계획은 최대 1회 허용된다. 따라서 두 하네스는 주로 컨텍스트 관리, 종료 조건, 오류 복구 축에서 다르다. 도구 granularity는 동일하며, 읽기 전용 도구만 사용했으므로 사람의 개입 지점도 동일하게 0회였다.



실행 명령은 다음과 같다.



`python run\_ab.py --runs 3`



\## 2. Measurements



| run | harness | success | tokens | iters | interventions | note |

|---:|---|:---:|---:|---:|---:|---|

| 1 | react | O | 15395 | 5 | 0 | |

| 2 | react | O | 11782 | 4 | 0 | |

| 3 | react | O | 8838 | 4 | 0 | |

| 4 | plan\_exec | O | 15382 | 7 | 0 | replans=1 |

| 5 | plan\_exec | X | 225 | 1 | 0 | replans=0 |

| 6 | plan\_exec | X | 738 | 1 | 0 | replans=0 |



ReAct의 성공률은 3/3이며 평균 토큰은 12,005개, 평균 반복 횟수는 4.33회였다. Plan-then-Execute의 성공률은 1/3이며 평균 토큰은 약 5,448개, 평균 반복 횟수는 3회였다.



\## 3. Interpretation



이 태스크에서는 ReAct가 성공률에서 우세했다. ReAct는 세 번 모두 성공했으며, 매 단계의 Observation을 다음 판단에 반영하는 오류 복구 방식이 안정성에 기여했다. 반면 Plan-then-Execute는 한 번만 성공했고 두 번은 계획 생성 호출 1회 후 종료되었다. 5번과 6번 실행은 반복 횟수가 1이고 재계획도 0회이므로, 실행 단계에 들어가기 전 계획을 유효한 JSON으로 파싱하지 못한 실패임을 로그에서 확인할 수 있다. Plan 방식의 평균 토큰이 더 적은 것은 단순히 효율성이 높아서가 아니라 두 실행이 초기에 실패했기 때문이다. 유일하게 성공한 Plan 실행은 재계획 1회를 사용했고, ReAct의 각 성공 실행보다 많은 7회 호출과 15,382토큰을 소비했다. 따라서 이번 실험에서는 ReAct의 단계별 컨텍스트 갱신과 유연한 오류 복구가 성공률을 높였고, Plan-then-Execute의 엄격한 계획 형식과 제한된 재계획 조건은 초기 계획 오류에 취약하게 작용했다.

15 changes: 15 additions & 0 deletions submissions/26512076/week-02/TASK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Task

The same task for both harnesses. `run_ab.py` reads the `task:` and
`expected:` lines below. Write the success criterion before you run
anything, and do not change it after you see the results.

task: In app.log, which hour (HH:00) has the most ERROR lines? Answer with the hour in HH:00 form.

## Success criterion

A run succeeds when the final answer contains the hour with the most ERROR
lines in `app.log`, written as HH:00. `app.log` is the reference input; the
graded runs use it unchanged.

expected: 14:00
60 changes: 60 additions & 0 deletions submissions/26512076/week-02/app.log
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
2026-09-01 09:07:27 INFO job finished in 320ms
2026-09-01 09:11:56 ERROR NullReference in QuizService.score
2026-09-01 09:13:14 INFO health check ok
2026-09-01 09:26:45 INFO health check ok
2026-09-01 09:30:17 INFO health check ok
2026-09-01 09:32:05 INFO request served /api/quiz
2026-09-01 10:07:50 INFO session started
2026-09-01 10:12:15 INFO request served /api/quiz
2026-09-01 10:18:22 ERROR db connection refused
2026-09-01 10:23:48 WARN retrying upstream call
2026-09-01 10:53:22 WARN slow query 1.8s on rooms
2026-09-01 10:54:07 ERROR NullReference in QuizService.score
2026-09-01 10:57:10 INFO cache hit for room 4412
2026-09-01 11:23:35 ERROR payment webhook signature mismatch
2026-09-01 11:36:12 INFO cache hit for room 4412
2026-09-01 11:39:20 INFO request served /api/quiz
2026-09-01 11:40:27 WARN slow query 1.8s on rooms
2026-09-01 11:44:02 INFO health check ok
2026-09-01 11:56:13 INFO request served /api/quiz
2026-09-01 12:08:13 INFO request served /api/quiz
2026-09-01 12:28:08 ERROR upstream timeout after 5000ms
2026-09-01 12:32:19 INFO health check ok
2026-09-01 12:49:54 ERROR db connection refused
2026-09-01 12:50:06 WARN token near expiry
2026-09-01 12:53:48 ERROR upstream timeout after 5000ms
2026-09-01 12:59:50 WARN token near expiry
2026-09-01 13:24:27 INFO cache hit for room 4412
2026-09-01 13:26:02 INFO session started
2026-09-01 13:28:15 INFO request served /api/quiz
2026-09-01 13:42:13 ERROR db connection refused
2026-09-01 13:43:37 WARN slow query 1.8s on rooms
2026-09-01 13:59:54 ERROR upstream timeout after 5000ms
2026-09-01 14:04:06 ERROR NullReference in QuizService.score
2026-09-01 14:07:11 ERROR NullReference in QuizService.score
2026-09-01 14:15:56 ERROR upstream timeout after 5000ms
2026-09-01 14:19:13 INFO health check ok
2026-09-01 14:26:19 INFO request served /api/quiz
2026-09-01 14:30:20 ERROR db connection refused
2026-09-01 14:43:37 INFO job finished in 320ms
2026-09-01 14:47:03 ERROR upstream timeout after 5000ms
2026-09-01 14:54:53 ERROR upstream timeout after 5000ms
2026-09-01 15:04:23 INFO cache hit for room 4412
2026-09-01 15:33:16 INFO request served /api/quiz
2026-09-01 15:34:38 ERROR payment webhook signature mismatch
2026-09-01 15:39:25 INFO cache hit for room 4412
2026-09-01 15:41:14 ERROR NullReference in QuizService.score
2026-09-01 15:45:36 INFO cache hit for room 4412
2026-09-01 15:48:41 WARN token near expiry
2026-09-01 16:11:35 INFO request served /api/quiz
2026-09-01 16:16:04 INFO cache hit for room 4412
2026-09-01 16:24:31 WARN slow query 1.8s on rooms
2026-09-01 16:44:46 INFO session started
2026-09-01 16:51:30 WARN retrying upstream call
2026-09-01 16:53:34 ERROR NullReference in QuizService.score
2026-09-01 17:17:14 ERROR NullReference in QuizService.score
2026-09-01 17:27:48 WARN token near expiry
2026-09-01 17:37:46 INFO health check ok
2026-09-01 17:48:38 WARN token near expiry
2026-09-01 17:55:29 INFO job finished in 320ms
2026-09-01 17:56:05 INFO cache hit for room 4412
98 changes: 98 additions & 0 deletions submissions/26512076/week-02/harness_plan_execute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""Week 02 starter — the Plan-then-Execute harness.

One call produces the whole plan as a JSON list. Then each step is executed
in order with tools. If a step reports OFF_PLAN, the plan is rebuilt once
(max_replan=1): that number is the flexibility cap, and it is explicit.
"""
import json
import re
import sys

from tools_shared import Chat, Meter, Reply

SYSTEM_PLAN = (
"You are a planner. Reply with a JSON list of short strings, one per step, "
"and nothing else. No prose, no code fences."
)
SYSTEM_EXEC = (
"You execute one step of a plan at a time with the tools you are given. "
"If the step cannot be done as planned, reply with a line that starts with "
"'OFF_PLAN:' and explain why. When asked for the final answer, reply with a "
"line that starts with 'Answer:'."
)


def parse_plan(text: str):
"""Return a list of step strings, or None if the model did not give JSON."""
text = re.sub(r"^```(?:json)?|```$", "", text.strip(), flags=re.M).strip()
try:
plan = json.loads(text)
except json.JSONDecodeError:
return None
if isinstance(plan, list) and all(isinstance(s, str) for s in plan):
return plan
return None


def run_plan_execute(task: str, max_replan: int = 1,
max_tool_rounds: int = 3, log=print):
meter = Meter()

# 1) PLAN: the whole plan in one call, no tools
planner = Chat(SYSTEM_PLAN, meter, tools=False)
planner.add_user(f"Task: {task}\nAvailable tools: read_file(path), "
f"count_pattern(path, pattern).")
raw = planner.send().text
plan = parse_plan(raw)
if plan is None: # a parse failure is one failure mode
log(f"[plan] not valid JSON: {raw.strip()[:300]!r}")
return "plan parse failed", meter, 0
log(f"[plan] {plan}")

# 2) EXECUTE: each step in order
executor = Chat(SYSTEM_EXEC, meter)
executor.add_user(f"Task: {task}\nPlan: {json.dumps(plan)}")
replans = 0
i = 0
while i < len(plan):
executor.add_user(f"Execute step {i + 1}: {plan[i]}")
reply = executor.send()
rounds = 0
while reply.tool_calls: # tool calls inside one step
executor.run_tools(reply, log)
reply = executor.send()
rounds += 1
if rounds >= max_tool_rounds and reply.tool_calls:
executor.run_tools(reply, log) # answer every call so the transcript stays valid
reply = Reply("OFF_PLAN: step exceeded the tool-call budget", [])
break
log(f"[step {i + 1}] {reply.text.strip()[:300]}")

if reply.text.strip().startswith("OFF_PLAN") and replans < max_replan:
replans += 1 # flexibility cap
planner.add_user(f"Step {i + 1} ({plan[i]}) failed: {reply.text.strip()[:300]}\n"
f"Reply with a JSON list of the remaining steps.")
raw = planner.send().text
new_steps = parse_plan(raw)
if new_steps is None:
log(f"[replan] not valid JSON: {raw.strip()[:300]!r}")
break
plan = plan[:i] + new_steps
log(f"[replan] {plan}")
continue
i += 1

executor.add_user("Give the final answer now, starting with 'Answer:'.")
final = executor.send()
if final.tool_calls: # the model tried to keep going
executor.run_tools(final, log)
final = executor.send()
return final.text, meter, replans


if __name__ == "__main__":
task = sys.argv[1] if len(sys.argv) > 1 else \
"In app.log, which hour (HH:00) has the most ERROR lines? Answer with the hour in HH:00 form."
answer, m, replans = run_plan_execute(task)
print(answer)
print(f"tokens={m.tokens} iters={m.iters} interventions={m.interventions} replans={replans}")
60 changes: 60 additions & 0 deletions submissions/26512076/week-02/harness_react.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Week 02 starter — the ReAct-style harness.

Every step: the model writes a Thought, calls a tool (Action), sees the result
(Observation), and decides again. The five axes from the lecture are marked.
"""
import sys

from tools_shared import Chat, Meter

SYSTEM = (
"You solve tasks with the tools you are given. Before every tool call, "
"write one line that starts with 'Thought:' saying what you know and what "
"you will do next. When the task is complete, reply with a line that starts "
"with 'Answer:' and make no tool call."
)

# [axis 5] calls that must be approved by a human before they run.
# The starter tools are read-only, so this set is empty and interventions
# stay at 0. Add a tool that writes or deletes, and put its name here.
IRREVERSIBLE = set()


def ask_human(call) -> bool:
answer = input(f"approve {call.name}({call.args})? [y/N] ").strip().lower()
return answer == "y"


def run_react(task: str, max_steps: int = 8, log=print):
meter = Meter()
chat = Chat(SYSTEM, meter) # [axis 1] context: full history, every call
chat.add_user(task)

for step in range(max_steps): # [axis 3] termination: iteration cap
reply = chat.send()
if reply.text.strip():
log(f"[step {step + 1}] {reply.text.strip()}")

if not reply.tool_calls: # [axis 3] the model chose to finish
return reply.text, meter

approved = []
for call in reply.tool_calls:
if call.name in IRREVERSIBLE and not ask_human(call):
meter.interventions += 1 # [axis 5] intervention point
chat.add_tool_result(call, "denied: human did not approve")
continue
approved.append(call)
reply.tool_calls = approved
chat.run_tools(reply, log) # [axis 2] granularity lives in tools_shared
# [axis 4] errors come back as Observations

return "MAX_STEPS reached: incomplete", meter


if __name__ == "__main__":
task = sys.argv[1] if len(sys.argv) > 1 else \
"In app.log, which hour (HH:00) has the most ERROR lines? Answer with the hour in HH:00 form."
answer, m = run_react(task)
print(answer)
print(f"tokens={m.tokens} iters={m.iters} interventions={m.interventions}")
16 changes: 16 additions & 0 deletions submissions/26512076/week-02/logs/plan_exec-04.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[plan] ['Read app.log', 'For each hour 00-23, count lines matching "^HH:00.*ERROR"', 'Select hour with highest count', 'Return that hour in HH:00 format']
[tool] read_file({'path': 'app.log'}) -> 2026-09-01 09:07:27 INFO job finished in 320ms | 2026-09-01 09:11:56 ERROR NullReference in QuizService.score | 2026-09-01 09:13:14 INFO health check ok | 2026-09-01 09:26:45 INFO health check ok | 2026-09
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 00:00.*ERROR'}) -> 0
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 09:.*ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 10:.*ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 11:.*ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 12:.*ERROR'}) -> 3
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 13:.*ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 14:.*ERROR'}) -> 6
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 15:.*ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 16:.*ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 17:.*ERROR'}) -> 1
[step 1] OFF_PLAN: step exceeded the tool-call budget
[replan] not valid JSON: 'User Safety: safe\nResponse Safety: safe'
[final] Answer: 14:00
[judge] expected='14:00' -> O (95.2s)
3 changes: 3 additions & 0 deletions submissions/26512076/week-02/logs/plan_exec-05.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[plan] not valid JSON: '{"analysis": "I need to find which hour has the most ERROR lines in app.log. I\'ll first read the file to understand its format, then count ERROR lines per hour.", "plan": "1. Read app.log to see the format and identify ERROR lines.\\n2. Use count_pattern to count ERROR lines for each hour.\\n3. Determ'
[final] plan parse failed
[judge] expected='14:00' -> X (1.1s)
3 changes: 3 additions & 0 deletions submissions/26512076/week-02/logs/plan_exec-06.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[plan] not valid JSON: 'User Safety: safe'
[final] plan parse failed
[judge] expected='14:00' -> X (2.9s)
16 changes: 16 additions & 0 deletions submissions/26512076/week-02/logs/react-01.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[tool] read_file({'path': 'app.log'}) -> 2026-09-01 09:07:27 INFO job finished in 320ms | 2026-09-01 09:11:56 ERROR NullReference in QuizService.score | 2026-09-01 09:13:14 INFO health check ok | 2026-09-01 09:26:45 INFO health check ok | 2026-09
[tool] count_pattern({'pattern': '^', 'path': 'app.log'}) -> 60
[step 3] Let me count ERROR lines by hour:
[tool] count_pattern({'path': 'app.log', 'pattern': 'ERROR'}) -> 19
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 09:\\d{2}:\\d{2}.*ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 10:\\d{2}:\\d{2}.*ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 11:\\d{2}:\\d{2}.*ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 12:\\d{2}:\\d{2}.*ERROR'}) -> 3
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 13:\\d{2}:\\d{2}.*ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 14:\\d{2}:\\d{2}.*ERROR'}) -> 6
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 15:\\d{2}:\\d{2}.*ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 16:\\d{2}:\\d{2}.*ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 17:\\d{2}:\\d{2}.*ERROR'}) -> 1
[step 5] Answer: 14:00
[final] Answer: 14:00
[judge] expected='14:00' -> O (94.3s)
13 changes: 13 additions & 0 deletions submissions/26512076/week-02/logs/react-02.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[tool] read_file({'path': 'app.log'}) -> 2026-09-01 09:07:27 INFO job finished in 320ms | 2026-09-01 09:11:56 ERROR NullReference in QuizService.score | 2026-09-01 09:13:14 INFO health check ok | 2026-09-01 09:26:45 INFO health check ok | 2026-09
[tool] count_pattern({'path': 'app.log', 'pattern': 'ERROR'}) -> 19
[tool] count_pattern({'path': 'app.log', 'pattern': '09:[0-9]{2}:[0-9]{2} ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '10:[0-9]{2}:[0-9]{2} ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '11:[0-9]{2}:[0-9]{2} ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '12:[0-9]{2}:[0-9]{2} ERROR'}) -> 3
[tool] count_pattern({'path': 'app.log', 'pattern': '13:[0-9]{2}:[0-9]{2} ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '14:[0-9]{2}:[0-9]{2} ERROR'}) -> 6
[tool] count_pattern({'path': 'app.log', 'pattern': '15:[0-9]{2}:[0-9]{2} ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '17:[0-9]{2}:[0-9]{2} ERROR'}) -> 1
[step 4] Answer: 14:00
[final] Answer: 14:00
[judge] expected='14:00' -> O (23.1s)
17 changes: 17 additions & 0 deletions submissions/26512076/week-02/logs/react-03.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[step 1] Thought: I need to read the app.log file to find ERROR lines and count them by hour.

Let me first read the file to understand its structure.
[tool] read_file({'path': 'app.log'}) -> 2026-09-01 09:07:27 INFO job finished in 320ms | 2026-09-01 09:11:56 ERROR NullReference in QuizService.score | 2026-09-01 09:13:14 INFO health check ok | 2026-09-01 09:26:45 INFO health check ok | 2026-09
[step 2] Thought: I can see the log entries. Now I need to count ERROR lines by hour. Let me use a pattern to count ERROR lines for each hour to be precise.
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 09:.* ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 10:.* ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 11:.* ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 12:.* ERROR'}) -> 3
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 13:.* ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 14:.* ERROR'}) -> 6
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 15:.* ERROR'}) -> 2
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 16:.* ERROR'}) -> 1
[tool] count_pattern({'path': 'app.log', 'pattern': '^2026-09-01 17:.* ERROR'}) -> 1
[step 4] Answer: 14:00
[final] Answer: 14:00
[judge] expected='14:00' -> O (72.0s)
7 changes: 7 additions & 0 deletions submissions/26512076/week-02/results.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
run,harness,success,tokens,iters,interventions,note
1,react,O,15395,5,0,
2,react,O,11782,4,0,
3,react,O,8838,4,0,
4,plan_exec,O,15382,7,0,replans=1
5,plan_exec,X,225,1,0,replans=0
6,plan_exec,X,738,1,0,replans=0
Loading
Loading