When you need DesignExecute
PlanExecute only allows assignment statements. When a task needs a loop, switch the base class to DesignExecute. Everything else stays the same.
Before you start
Track 1 used PlanExecute. Its generated plans are pure assignment statements:
no for, no while, no if. That covers a lot of tasks. When a task
requires iteration, DesignExecute is the right base class instead.
The task#
Sum the squares of all integers from 1 to 100. The answer is 338,350.
No model will write 100 inline square() calls. The plan must loop.
PlanExecute rejects any plan that contains for or while:
PlanExecute rejects the plan: For statements are not allowed in plansDesignExecute allows it.
The agent#
Three primitives: square, add, and format_result. The only change from a
PlanExecute agent is the import and the base class.
# accumulator.py
from opensymbolicai.blueprints import DesignExecute
from opensymbolicai.core import primitive
class Accumulator(DesignExecute):
"""Iterative math agent: accumulate values over a range."""
@primitive(read_only=True)
def square(self, n: int) -> int:
"""Return n squared."""
return n * n
@primitive(read_only=True)
def add(self, a: int, b: int) -> int:
"""Add two integers."""
return a + b
@primitive(read_only=True)
def format_result(self, label: str, value: int) -> str:
"""Format a labeled result line."""
return f"{label}: {value}"Run it#
# main.py
from accumulator import Accumulator
from opensymbolicai.llm import LLMConfig
TASK = "What is the sum of the squares of all integers from 1 to 100?"
llm = LLMConfig(provider="ollama", model="qwen2.5-coder:7b")
agent = Accumulator(llm=llm)
result = agent.run(TASK)
print("--- plan ---")
print(result.plan)
print()
print("--- result ---")
print(result.result)
print(f"({len(result.trace.steps)} primitive calls)")uv run main.pyOutput:
--- plan ---
total = 0
for i in range(1, 101):
square_value = square(i)
total = add(total, square_value)
result_line = format_result("Sum of squares from 1 to 100", total)
return result_line
--- result ---
Sum of squares from 1 to 100: 338350
(201 primitive calls)201 calls: one square and one add per iteration, plus one format_result
at the end.
What DesignExecute permits#
DesignExecute allows for, while, if/elif/else, try/except, and
raise in model-generated plans. Everything else that PlanExecute blocks
remains blocked: no imports, no function definitions, no exec or eval.
Proving PlanExecute rejects the plan#
Feed the generated plan into a PlanExecute agent's validate_plan:
try:
pe_agent.validate_plan(result.plan)
except ValueError as e:
print(f"PlanExecute rejects the plan: {e}")PlanExecute rejects the plan: For statements are not allowed in plansValidation happens before any primitive runs, so nothing executes.