Tier 1 of the architecture is one optimizable dspy.Module per agent. Each lens wraps a signature. Where the app’s constraints matter (3–5 children, valid taxonomy types, non-empty labels), the lens is wrapped in dspy.Refine — the 3.x replacement for the removed Assert / Suggest. Refine retries with auto-generated feedback (hint_ field) until a deterministic reward function passes.
The lens set
Package: qlaw/lenses/.
Deterministic rewards for Refine
Reward functions live in qlaw/lenses/_validators.py and return 0.0 or 1.0. They enforce the app’s structural contract — item count, non-empty labels, allowed types — without an LM in the loop.
Example lens: SemanticInterpreter
Example lens: Decomposer (Refine-validated)
Signatures
Every lens ships with a typed dspy.Signature. Signatures live in qlaw/signatures.py and use pydantic fields for the output contract. The docstring is the instructions; the input and output fields are the schema. Output DTOs (SubNode, Concept, Trajectory, Risk, Intel) pin Literal[NodeType.…] so coercion enforces the type discipline at the boundary.
Why lenses are optimizable
Because each lens is a dspy.Module with a Signature, it can be:
- Compiled with
dspy.MIPROv2 or dspy.BootstrapFewShot from a per-lens trainset.
- Evaluated with
dspy.Evaluate on the matching devset in qlaw/datasets.py.
- Scored with the six metrics in
qlaw/metrics.py: taxonomy adherence, node validity, conciseness, novelty, coverage, and grounding.
- Loaded back into the engine after compile, replacing the zero-shot module in-place.
See Optimization and evaluation for the compile pipeline. Last modified on August 9, 2026