From 5832b907c660ae06e9a2524d4631ca6b8080054a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Mon, 26 Jan 2026 18:43:47 +0100 Subject: [PATCH] fix(ui): reflections based on don't show up all contents (#203) --- hindsight-api/hindsight_api/api/http.py | 48 ----- .../hindsight_api/engine/memory_engine.py | 23 +-- .../src/components/reflections-view.tsx | 191 +++++++++++------- 3 files changed, 120 insertions(+), 142 deletions(-) diff --git a/hindsight-api/hindsight_api/api/http.py b/hindsight-api/hindsight_api/api/http.py index efdd9031..6350965b 100644 --- a/hindsight-api/hindsight_api/api/http.py +++ b/hindsight-api/hindsight_api/api/http.py @@ -554,18 +554,6 @@ class ReflectLLMCall(BaseModel): duration_ms: int = Field(description="Execution time in milliseconds") -class ReflectMentalModel(BaseModel): - """A mental model accessed during reflect.""" - - id: str = Field(description="Mental model ID") - name: str = Field(description="Mental model name") - type: str = Field(description="Mental model type: entity, concept, event") - subtype: str = Field(description="Mental model subtype: structural, emergent, learned, directive") - observations: list[str] | None = Field( - default=None, description="Observations for directive mental models (subtype='directive')" - ) - - class ReflectBasedOn(BaseModel): """Evidence the response is based on: memories and mental models.""" @@ -577,10 +565,6 @@ class ReflectTrace(BaseModel): tool_calls: list[ReflectToolCall] = Field(default_factory=list, description="Tool calls made during reflection") llm_calls: list[ReflectLLMCall] = Field(default_factory=list, description="LLM calls made during reflection") - mental_models: list[ReflectMentalModel] = Field( - default_factory=list, - description="Mental models used during reflection (includes directives with subtype='directive')", - ) class ReflectResponse(BaseModel): @@ -604,14 +588,6 @@ class ReflectResponse(BaseModel): "trace": { "tool_calls": [{"tool": "recall", "input": {"query": "AI"}, "duration_ms": 150}], "llm_calls": [{"scope": "agent_1", "duration_ms": 1200}], - "mental_models": [ - { - "id": "mm-1", - "name": "AI Technology", - "type": "concept", - "subtype": "structural", - } - ], }, } } @@ -1894,33 +1870,9 @@ def _register_routes(app: FastAPI): for tc in core_result.tool_trace ] llm_calls = [ReflectLLMCall(scope=lc.scope, duration_ms=lc.duration_ms) for lc in core_result.llm_trace] - # Build map of directive observations by id - directive_observations = {d.id: d.rules for d in core_result.directives_applied} - # Build mental models from tool trace (get_mental_model outputs) - trace_mental_models: list[ReflectMentalModel] = [] - seen_model_ids: set[str] = set() - for tc in core_result.tool_trace: - if tc.tool == "get_mental_model" and tc.output.get("found") and "model" in tc.output: - model = tc.output["model"] - model_id = model.get("id") - if model_id and model_id not in seen_model_ids: - seen_model_ids.add(model_id) - model_subtype = model.get("subtype", "structural") - trace_mental_models.append( - ReflectMentalModel( - id=model_id, - name=model.get("name", ""), - type=model.get("type", "concept"), - subtype=model_subtype, - observations=directive_observations.get(model_id) - if model_subtype == "directive" - else None, - ) - ) trace_result = ReflectTrace( tool_calls=tool_calls, llm_calls=llm_calls, - mental_models=trace_mental_models, ) return ReflectResponse( diff --git a/hindsight-api/hindsight_api/engine/memory_engine.py b/hindsight-api/hindsight_api/engine/memory_engine.py index 1be8cd6e..b322ee7f 100644 --- a/hindsight-api/hindsight_api/engine/memory_engine.py +++ b/hindsight-api/hindsight_api/engine/memory_engine.py @@ -613,10 +613,6 @@ class MemoryEngine(MemoryEngineInterface): ] for fact_type, facts in reflect_result.based_on.items() }, - # Extract mental models from based_on["mental-models"] for easy UI access - "mental_models": [ - {"id": str(fact.id), "text": fact.text} for fact in reflect_result.based_on.get("mental-models", []) - ], } # Update the reflection with the generated content and reflect_response @@ -686,10 +682,6 @@ class MemoryEngine(MemoryEngineInterface): ] for fact_type, facts in reflect_result.based_on.items() }, - # Extract mental models from based_on["mental-models"] for easy UI access - "mental_models": [ - {"id": str(fact.id), "text": fact.text} for fact in reflect_result.based_on.get("mental-models", []) - ], } # Update the reflection with the generated content and reflect_response @@ -3722,14 +3714,13 @@ class MemoryEngine(MemoryEngineInterface): continue # Skip models not actually used by the agent seen_model_ids.add(model_id) # Add to based_on as MemoryFact with type "mental-models" - model_name = model.get("name", "") - model_summary = model.get("summary") or model.get("description", "") + # Mental models have a "text" field containing the consolidated knowledge based_on["mental-models"].append( MemoryFact( id=model_id, - text=f"{model_name}: {model_summary}", + text=model.get("text", ""), fact_type="mental-models", - context=f"{model.get('type', 'concept')} ({model.get('subtype', 'structural')})", + context=None, occurred_start=None, occurred_end=None, ) @@ -3744,14 +3735,13 @@ class MemoryEngine(MemoryEngineInterface): continue # Skip models not actually used by the agent seen_model_ids.add(model_id) # Add to based_on as MemoryFact with type "mental-models" - model_name = model.get("name", "") - model_summary = model.get("summary") or model.get("description", "") + # Mental models have a "text" field containing the consolidated knowledge based_on["mental-models"].append( MemoryFact( id=model_id, - text=f"{model_name}: {model_summary}", + text=model.get("text", ""), fact_type="mental-models", - context=f"{model.get('type', 'concept')} ({model.get('subtype', 'structural')})", + context=None, occurred_start=None, occurred_end=None, ) @@ -5021,7 +5011,6 @@ class MemoryEngine(MemoryEngineInterface): ] for fact_type, facts in reflect_result.based_on.items() }, - "mental_models": [], # Mental models are included in based_on["mental-models"] } # Update the reflection with new content and reflect_response diff --git a/hindsight-control-plane/src/components/reflections-view.tsx b/hindsight-control-plane/src/components/reflections-view.tsx index bf62a1c3..8460c4c7 100644 --- a/hindsight-control-plane/src/components/reflections-view.tsx +++ b/hindsight-control-plane/src/components/reflections-view.tsx @@ -33,6 +33,7 @@ import { TableHeader, TableRow, } from "@/components/ui/table"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus, Sparkles, @@ -57,7 +58,6 @@ interface ReflectResponseBasedOnFact { interface ReflectResponse { text: string; based_on: Record; - mental_models?: Array<{ id: string; text: string }>; } interface Reflection { @@ -616,14 +616,12 @@ function ReflectionDetailPanel({ })}`; }; - // Extract all memories from based_on - const basedOnFacts = reflection.reflect_response?.based_on - ? Object.entries(reflection.reflect_response.based_on).flatMap(([factType, facts]) => - facts.map((fact) => ({ ...fact, factType })) - ) - : []; - - const mentalModels = reflection.reflect_response?.mental_models || []; + // Extract facts by type from based_on + const basedOn = reflection.reflect_response?.based_on || {}; + const worldFacts = basedOn["world"] || []; + const experienceFacts = basedOn["experience"] || []; + const mentalModels = basedOn["mental-models"] || []; + const totalFacts = worldFacts.length + experienceFacts.length + mentalModels.length; return (
@@ -703,80 +701,119 @@ function ReflectionDetailPanel({
- {/* Based On Facts Section */} - {basedOnFacts.length > 0 && ( + {/* Based On Section with Tabs */} + {totalFacts > 0 ? (
- Based On ({basedOnFacts.length} {basedOnFacts.length === 1 ? "fact" : "facts"}) + Based On ({totalFacts} {totalFacts === 1 ? "item" : "items"})
-
- {basedOnFacts.map((fact, i) => ( -
-
- - {fact.factType} - - -
-

{fact.text}

-
- ))} -
-
- )} + 0 + ? "world" + : experienceFacts.length > 0 + ? "experience" + : "mental-models" + } + > + + + World + + {worldFacts.length} + + + + Experience + + {experienceFacts.length} + + + + Mental Models + + {mentalModels.length} + + + - {/* Mental Models Used Section */} - {mentalModels.length > 0 && ( -
-
- Mental Models Used ({mentalModels.length}) -
-
- {mentalModels.map((model, i) => ( -
-
- - mental_model - - -
-

{model.text}

+
+

+ {fact.text} +

+ +
+
+ ))}
- ))} -
- - )} + - {/* No based_on data yet */} - {!reflection.reflect_response && ( + +
+ {experienceFacts.map((fact, i) => ( +
+
+

+ {fact.text} +

+ +
+
+ ))} +
+
+ + +
+ {mentalModels.map((model, i) => ( +
+
+

+ {model.text} +

+ +
+
+ ))} +
+
+
+ + ) : !reflection.reflect_response ? (
Based On

@@ -784,7 +821,7 @@ function ReflectionDetailPanel({ tracking.

- )} + ) : null} {reflection.tags && reflection.tags.length > 0 && (