Visual Epistemic Field Generation
A visual AI pipeline for comparing populations of reasoning traces: gather good and contrasting examples, render VPM timelines, subtract fields, and rank the metrics that separate them.
The solution is backed by inspectable code
This solves visual debugging for reasoning systems. Instead of reading thousands of scalar scores, render trace populations as comparable images and inspect the difference between good, mixed, and opposite reasoning.
Code
class PhosAgent:
def __init__(self, memory, zero_model, scorer, logger):
self.memory = memory
self.zm = zero_model
self.scorer = scorer
self.logger = logger
self.metric_names = ["alignment", "coherence", "depth", "novelty"]
async def run(self, context):
run_id = context["pipeline_run_id"]
goal_id = context["goal"]["id"]
good = await self.gather_good_runs(goal_id)
medium = self.memory.embedding.search_similarity_band(goal_id, low=0.15, high=0.80)
opposite = self.memory.embedding.search_unrelated_scorables(goal_id, limit=300)
datasets = {"good": good, "medium": medium, "opposite": opposite}
vpms = await self.render_bands(run_id, datasets, context)
context["phos_outputs"] = self.analyze_vpms(run_id, vpms)
return context
async def gather_good_runs(self, goal_id):
rows = self.memory.prompts.for_goal(goal_id=goal_id, limit=300)
return [{"response": self.strip_think_blocks(row.response_text)} for row in rows]
async def render_bands(self, run_id, datasets, context):
results = {}
for label, scorables in datasets.items():
self.zm.timeline_open(run_id=f"{run_id}:{label}")
for scorable in scorables:
metrics = await self.scorer.score(scorable, context)
self.zm.timeline_add(run_id=f"{run_id}:{label}", metrics=metrics)
results[label] = self.zm.timeline_finalize(
run_id=f"{run_id}:{label}",
out_path=f"data/phos/{run_id}/{label}.gif",
)
return results
def analyze_vpms(self, run_id, vpms):
good_mat = vpms["good"]["matrix"]
opposite_mat = vpms["opposite"]["matrix"]
meta = self.zm.generate_epistemic_field(
pos_matrices=[good_mat],
neg_matrices=[opposite_mat],
output_dir=f"data/phos/{run_id}/diffs",
metric_names=self.metric_names,
)
ranked = self.zm.analyze_differential_field(
meta["diff_matrix"],
meta["metric_names_reordered"],
output_dir=f"data/phos/{run_id}/ranked",
)
return {"good_vs_opposite": meta, "ranked_metrics": ranked}
@staticmethod
def strip_think_blocks(text):
return text.replace("<think>", "").replace("</think>", "")
Usage
context = await PhosAgent(memory, zero_model, scorer, logger).run({
"pipeline_run_id": "run-2026-09-09",
"goal": {"id": 42, "goal_text": "How can an agent improve its evidence use?"},
})
print(context["phos_outputs"]["ranked_metrics"])
How it works
The important split is by population, not by single example. Good traces form the positive field. Medium or unrelated traces form contrast classes. ZeroModel renders each population into VPM timelines, then Phos computes differential fields that show which metrics survive the comparison.
Source
The article points to the implementation in stephanie/agents/phos.py.
Full explanation
For the complete stack including ATS, MetricsWorker, VPMWorker, ZeroModel, and Phos, read: A Complete Visual Reasoning Stack: From Conversations to Epistemic Fields.