dspy.Signature + dspy.Module lenses over a pydantic graph state. The user-driven loop (Selection → Lens Invocation → Expansion) becomes the forward() of a single ReasoningEngine module. The Co-Pilot and the Enrich researcher become dspy.ReActV2 tool agents.
Because everything is a DSPy module, every layer is:
- Optimizable — via
MIPROv2,BootstrapFewShot, or GEPA. - Evaluable — via
dspy.Evaluatewith custom metrics. Refine-validated — deterministic reward functions retry until the contract passes.
Three tiers
DSPy 3.3.0 primitives used
Layer-to-DSPy mapping
Domain model
The graph is the typed state that flows through every DSPy module. It has two hard rules:- Expansion is deterministic and pure. The LM never mutates the graph. It emits a
NodeBatch, andGraphState.apply()does the wiring. The graph delta is ground truth a metric can check. - The LM sees a distilled
NodeContext, not raw state. Lenses receivenode.labelandnode.description. The Co-Pilot gets a graph summary.
NodeType: ROOT, PROBLEM, QUESTION, PLAN, PROJECT, COMPONENT, TRAJECTORY, DATA, RISK, INSIGHT, CONCEPT, VISUALIZATION. Lens output DTOs (SubNode, Concept, Trajectory, Risk, Intel) each pin a Literal[NodeType.…] so pydantic coercion enforces the contract at the boundary.
Model configuration
One model across all tiers —deepseek-v4-flash via the OpenAI-compatible endpoint, configured once at startup.
Set
model_type="chat" if the endpoint requires it. Any OpenAI-compatible model id can be swapped in via OPENAI_MODEL.