Repository navigation
fix(core): count a repeat as a loop only when its output repeats too - #47
Conversation
LoopGuard ended a task after three turns with the same action signature, whatever the actions returned. A resumable job is driven exactly that way: one command, run again until it reports done, each call advancing state on disk. In two long unattended benchmark sessions the guard stopped such a fit at its third call, though every call had run new evaluations. A repeat now needs the same output as well as the same call. The stall rule (five turns in a row with the same output, whatever the calls) is unchanged, and so is the snapshot, so older checkpoints resume as before. Replayed over 943 recorded sessions (8,236 tool turns), the unpatched guard reproduces every recorded stop. The patched guard keeps all 7 stops from the Aider polyglot runs, each an identical result, and drops the 2 from the long sessions.
|
Reviewed Both new tests fail with the base implementation and pass with this head. I also ran the actual built CLI against a local HTTP fixture and real Frozen install, typecheck, build and tool-schema lint passed. The full Mac run passed 1,669 tests and hit four existing process-start timeout cases under concurrent suites; all four passed in an isolated 31-test rerun. Loop and resume coverage passed. CI on this exact head is green in all five jobs: https://github.com/TaewoooPark/Motifcode/actions/runs/39023298505 The documented tradeoff remains: changing incidental output can avoid repeat detection, with the turn limit still applying. Thanks for the focused fix and regression coverage. Merging as a squash commit; npm 0.4.3 publication will follow separately after combined release validation. |
Problem
The DW-ID micromagnetics benchmark runs a long unattended session: fit four material constants to measured domain-wall data with mumax3, using the spinloop skills, under a 6 h cap. motifcode ended the task twice while the agent was doing exactly what the fitting skill tells it to do.
The skill's fit is resumable.
fit.py step SPEC --budget Nruns for at most N seconds, and the next call continues the fit. The skill says "call it again until it says done", and the tool ends every call with:LoopGuardcounted a repeat whenever a turn's action signature matched the turn before, whatever the actions returned. The third identicalstepcall therefore ended the task (session_end: loop_detected). Nobody was at the keyboard, so the session then sat idle until the benchmark's quiet rule closed it.The three calls before the stop each finished new evaluations:
The two sessions were stopped 4.2 h and 2.3 h into their 6 h budget.
The intent was already the narrower rule. The guard's test is named "stops when the same action repeats with the same result", and
packages/eval/REPORT.mddescribes it as "identical call/output repeated". The code, however, compared the call only.Change
fix(core): count a repeat as a loop only when its output repeats tooLoopGuardSnapshotis unchanged, so checkpoints from earlier versions resume as before.Evidence
Every recorded motifcode journal was replayed through both versions of
LoopGuard, turn by turn asrunLoopfeeds it. That is 943 sessions and 8,236 tool turns: 936 from the Aider polyglot campaign and its follow-up runs, and 7 from the micromagnetics runs.The unpatched guard stopped exactly where the journals recorded a stop, and nowhere else. This validates the replay.
All 7 polyglot stops were real loops with an unchanged result:
write, orcat >in bash);g++failing on the same error, acargo testmatching no tests);spawn /bin/sh ENOENT, asederror).None of them depends on comparing the call alone, so the polyglot results do not move.
In those 936 sessions, no call was repeated three times with a changing result. The protection this change gives up was never used there.
What the change gives up. A call that repeats with output that changes but carries nothing new, such as a timestamp or a timing, is no longer stopped by this rule. Such a run is still bounded by the turn ceiling (100 by default) and, in interactive use, by the user.
Alternatives considered
repeatLimit. Any fixed number still ends a fit that needs more steps; a passing run of this task needed 13stepcalls in a row. Real loops would also run longer before being stopped.Tests
pnpm typecheck,pnpm test(1673 passed),pnpm lint:toolsandpnpm buildall pass.The two new tests both fail on
main:stepcalls whose output changes, and endsdonewith noloop_detected.LoopGuardis checked directly: