The loop guard and max_loop_iterations
Every loop in a DesignExecute plan has a built-in iteration counter. DesignExecuteConfig(max_loop_iterations=N) sets the cap. Trip it to see the error; raise it to let the task finish.
Before you start
Track 17 switched the base class to DesignExecute to allow loops. This
tutorial shows what keeps those loops from running forever: an AST-injected
iteration counter that stops the loop if it exceeds max_loop_iterations.
The plan#
The sum-of-squares plan from Track 17 loops exactly 100 times. Pass it
directly to execute() so the result is deterministic regardless of what
model you use.
PLAN = """\
total = 0
for i in range(1, 101):
sq = square(i)
total = add(total, sq)
return format_result("Sum of squares 1 to 100", total)
"""Setting the limit#
from opensymbolicai.models import DesignExecuteConfig
config = DesignExecuteConfig(max_loop_iterations=N)
agent = Accumulator(llm=llm, config=config)
result = agent.execute(PLAN)Tripping the guard#
With max_loop_iterations=50, the loop body runs 50 times and fails on
iteration 51:
config = DesignExecuteConfig(max_loop_iterations=50)
agent = Accumulator(llm=llm, config=config)
result = agent.execute(PLAN)
last_step = result.trace.steps[-1]
print(last_step.error)Loop exceeded maximum iterations (50)Letting it finish#
With max_loop_iterations=100, all 100 iterations fit:
config = DesignExecuteConfig(max_loop_iterations=100)
agent = Accumulator(llm=llm, config=config)
result = agent.execute(PLAN)
if result.trace.all_succeeded:
print(result.get_value())
print(f"primitive calls: {len(result.trace.steps)}")Sum of squares 1 to 100: 338350
primitive calls: 201Full script#
# main.py
from accumulator import Accumulator
from opensymbolicai.llm import LLMConfig
from opensymbolicai.models import DesignExecuteConfig
PLAN = """\
total = 0
for i in range(1, 101):
sq = square(i)
total = add(total, sq)
return format_result("Sum of squares 1 to 100", total)
"""
def run(label: str, max_loop_iterations: int) -> None:
llm = LLMConfig(provider="ollama", model="qwen2.5-coder:7b")
config = DesignExecuteConfig(max_loop_iterations=max_loop_iterations)
agent = Accumulator(llm=llm, config=config)
result = agent.execute(PLAN)
last_step = result.trace.steps[-1]
print(f"--- {label} (max_loop_iterations={max_loop_iterations}) ---")
if result.trace.all_succeeded:
print("result:", result.get_value())
print(f"primitive calls: {len(result.trace.steps)}")
else:
print("error:", last_step.error)
print()
run("limit too small", max_loop_iterations=50)
run("limit sufficient", max_loop_iterations=100)uv run main.pyThe default is 100. Pick a limit that fits the largest range your tasks will ever iterate.