dspy.Module. The current React app drives the loop from App.tsx click handlers; here forward() is the loop:
forward() is a sub-module, so the entire engine is compilable and evaluable as a unit. With dspy.Flex, even the loop structure itself can be optimized by GEPA.
Router
qlaw/router.py picks the lens for a given node and decides whether to stop the recursion. Both are DSPy modules; a deterministic heuristic covers the case where no router is configured.
Reasoning engine
qlaw/engine.py composes ReasoningEngine + ReasoningLoop + SeedFlow — the whole cycle as one program.
SeedFlow— the compositionSemanticInterpreter → Decomposerthat produces the initialROOTnode plus its first decomposition layer.ReasoningEngine— one step of the cycle: Selection → Routing → Invocation → Expansion → Critic gate.lens_batch()maps every lens output to aNodeBatch.ReasoningLoop— recursion withTerminator.max_depthis a constructor argument, not a request field.
NodeBatch; GraphState.apply() does the wiring deterministically.
One reasoning step
ReasoningEngine.forward(graph, active_node_id, action=None) returns a Prediction with:
Recursion depth is a constructor argument
ReasoningLoop.max_depth and ReasoningEngine.max_depth are set at construction time. They are not accepted on the /engine request body. This is one of the DSPy 3.3.0 gotchas — see Gotchas.
Why the engine is optimizable
Because the whole loop is a singledspy.Module:
dspy.MIPROv2ordspy.BootstrapFewShotcan compile the engine from full-session trajectories inqlaw/datasets.py::engine_trainset().dspy.Evaluatecan score full sessions with any subset of the six metrics inqlaw/metrics.py.dspy.Flex(experimental) starts from a signature, not a composed module. A future signature-first rewrite of the loop can be optimizer-authored inside aCodeInterpretersandbox.