Skip to content

Tutorial 1: Your First Loop

In this tutorial you will build the smallest possible Loop Engineering loop, run it, and read the result. No API key required - every component here is plain Python.

What you'll build

A loop with a single goal - "count the words in this sentence" - that plans one step, executes it, evaluates the result, and terminates. It is deliberately trivial: the point is to see the six moving parts before you put real work (or an LLM) behind any of them.

The six components

LoopEngine does not know how to plan, act, observe, evaluate, recover, or decide when to stop. You provide those as components, registered against a ComponentType:

Component Interface Job
PLANNER create_plan, revise_plan Turn a goal into Steps
ACTOR execute, execute_direct Do the actual work of a step
OBSERVER observe Turn a raw result into an Observation
EVALUATOR evaluate Decide whether the step's result is good enough
TERMINATOR should_terminate Decide whether the loop is done
RECOVERY (optional) recover Choose a strategy when something fails

loop_engine.components ships ready-to-use implementations for most of these (SimplePlanner, SimpleObserver, SimpleTerminator, LLMPlanner, LLMEvaluator, ...). The only thing genuinely specific to your problem is usually the Actor - and often the Evaluator, since "good enough" is a domain judgment call.

Step 1: Write your Actor

This is the one piece of domain logic this tutorial needs:

from loop_engine.components import Actor

class WordCountActor(Actor):
    """Domain logic: count words in a step's description."""

    async def execute(self, step, observations, context):
        return {"step": step.description, "word_count": len(step.description.split())}

    async def execute_direct(self, goal, context):
        return {"goal": goal, "word_count": len(goal.split())}

Step 2: Write a matching Evaluator

SimpleEvaluator (shipped in loop_engine.components) judges a step by plan progress, which assumes a VERIFIER component is also registered to promote steps to VERIFIED_COMPLETED. Since this tutorial has no verifier yet, write the two-line evaluator that actually matches what you're checking - "did the actor produce a result?":

from loop_engine.types import Evaluation
from loop_engine.components import Evaluator

class HasResultEvaluator(Evaluator):
    """Domain logic: the step passes once the actor produced a result."""

    async def evaluate(self, plan, observations, goal, context):
        if not observations:
            return Evaluation(score=0.0, passed=False, feedback="No result yet")
        return Evaluation(score=1.0, passed=True, feedback="Actor produced a result")

Why not just use SimpleEvaluator?

SimpleEvaluator and SimpleTerminator both key off Plan.get_progress(), which only counts steps in VERIFIED_COMPLETED or SKIPPED. That promotion normally happens in the verification phase. With enable_verification=False (below), the engine promotes a passed step directly - but only once your evaluator has actually said passed. Writing the two lines above is clearer than reasoning about that interaction, and it's exactly the kind of two-line domain evaluator you'll write for real steps too.

Step 3: Wire it into a LoopEngine

import asyncio
from loop_engine.core import LoopEngine, LoopConfig
from loop_engine.types import ComponentType, LoopContext, Budget
from loop_engine.components import SimplePlanner, SimpleObserver, SimpleTerminator

async def main():
    engine = LoopEngine(LoopConfig(max_iterations=5, enable_verification=False))

    engine.register_component(ComponentType.PLANNER, SimplePlanner())
    engine.register_component(ComponentType.ACTOR, WordCountActor())
    engine.register_component(ComponentType.OBSERVER, SimpleObserver())
    engine.register_component(ComponentType.EVALUATOR, HasResultEvaluator())
    engine.register_component(ComponentType.TERMINATOR, SimpleTerminator())

    context = LoopContext(
        goal="Count the words in this sentence",
        budget=Budget(max_steps=5),
    )

    result = await engine.run(context)

    print("status:", result.status.name)
    print("iterations:", result.iterations)
    print("output:", result.output)
    print("verification_status:", result.verification_status)

asyncio.run(main())

enable_verification=False tells the engine there's no separate verification phase in this loop - the evaluator's judgment is final. You'll add a real verifier in a later tutorial.

Step 4: Run it

python your_first_loop.py

Real, unedited output:

status: COMPLETED
iterations: 1
output: {'step': 'Count the words in this sentence', 'word_count': 6}
verification_status: True

What just happened

engine.run(context) drove the state machine through one full iteration:

INITIALIZED -> PLANNING -> ACTING -> OBSERVING -> EVALUATING -> ITERATION_COMPLETE -> COMPLETED
  1. PLANNING - SimplePlanner turned the goal into a one-step Plan.
  2. ACTING - WordCountActor.execute() ran and returned a dict.
  3. OBSERVING - SimpleObserver wrapped that dict in an Observation.
  4. EVALUATING - HasResultEvaluator saw an observation and passed the step. With verification disabled, the step was promoted straight to VERIFIED_COMPLETED.
  5. ITERATION_COMPLETE - SimpleTerminator saw Plan.get_progress() == 1.0 and the engine transitioned to COMPLETED.

Every one of those transitions is validated against an explicit table (LoopEngine._VALID_TRANSITIONS) - the engine cannot silently skip a phase or end in an inconsistent state. See the state machine specification for the full transition table.

What's next

This loop can't fail, so it never needed recovery. Real work fails constantly - APIs time out, generated code doesn't compile, evaluators reject a first draft. Tutorial 2 takes this same shape and adds a step that fails twice before succeeding, so you can watch the engine recover without losing the loop.