Compare commits
1
Commits
deadc-oder
...
fix-refl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb44a15122 |
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<string, ReflectResponseBasedOnFact[]>;
|
||||
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 (
|
||||
<div className="fixed right-0 top-0 h-screen w-1/2 bg-card border-l shadow-2xl z-50 overflow-y-auto animate-in slide-in-from-right duration-300 ease-out">
|
||||
@@ -703,80 +701,119 @@ function ReflectionDetailPanel({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Based On Facts Section */}
|
||||
{basedOnFacts.length > 0 && (
|
||||
{/* Based On Section with Tabs */}
|
||||
{totalFacts > 0 ? (
|
||||
<div className="border-t border-border pt-5">
|
||||
<div className="text-xs font-bold text-muted-foreground uppercase mb-3">
|
||||
Based On ({basedOnFacts.length} {basedOnFacts.length === 1 ? "fact" : "facts"})
|
||||
Based On ({totalFacts} {totalFacts === 1 ? "item" : "items"})
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{basedOnFacts.map((fact, i) => (
|
||||
<div
|
||||
key={fact.id || i}
|
||||
className="p-4 bg-muted/50 rounded-lg border border-border/50"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<span
|
||||
className={`px-2 py-0.5 rounded text-xs font-medium ${
|
||||
fact.factType === "world"
|
||||
? "bg-blue-500/10 text-blue-600 dark:text-blue-400"
|
||||
: fact.factType === "experience"
|
||||
? "bg-green-500/10 text-green-600 dark:text-green-400"
|
||||
: "bg-purple-500/10 text-purple-600 dark:text-purple-400"
|
||||
}`}
|
||||
>
|
||||
{fact.factType}
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs"
|
||||
onClick={() => setViewMemoryId(fact.id)}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-foreground leading-relaxed">{fact.text}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Tabs
|
||||
defaultValue={
|
||||
worldFacts.length > 0
|
||||
? "world"
|
||||
: experienceFacts.length > 0
|
||||
? "experience"
|
||||
: "mental-models"
|
||||
}
|
||||
>
|
||||
<TabsList className="mb-4">
|
||||
<TabsTrigger value="world" disabled={worldFacts.length === 0}>
|
||||
World
|
||||
<span className="ml-1.5 px-1.5 py-0.5 rounded-full text-xs bg-blue-500/10 text-blue-600 dark:text-blue-400">
|
||||
{worldFacts.length}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="experience" disabled={experienceFacts.length === 0}>
|
||||
Experience
|
||||
<span className="ml-1.5 px-1.5 py-0.5 rounded-full text-xs bg-green-500/10 text-green-600 dark:text-green-400">
|
||||
{experienceFacts.length}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="mental-models" disabled={mentalModels.length === 0}>
|
||||
Mental Models
|
||||
<span className="ml-1.5 px-1.5 py-0.5 rounded-full text-xs bg-amber-500/10 text-amber-600 dark:text-amber-400">
|
||||
{mentalModels.length}
|
||||
</span>
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
{/* Mental Models Used Section */}
|
||||
{mentalModels.length > 0 && (
|
||||
<div className="border-t border-border pt-5">
|
||||
<div className="text-xs font-bold text-muted-foreground uppercase mb-3">
|
||||
Mental Models Used ({mentalModels.length})
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
{mentalModels.map((model, i) => (
|
||||
<div
|
||||
key={model.id || i}
|
||||
className="p-4 bg-muted/50 rounded-lg border border-border/50"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2 mb-2">
|
||||
<span className="px-2 py-0.5 rounded text-xs font-medium bg-amber-500/10 text-amber-600 dark:text-amber-400">
|
||||
mental_model
|
||||
</span>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs"
|
||||
onClick={() => setViewMemoryId(model.id)}
|
||||
<TabsContent value="world">
|
||||
<div className="space-y-3">
|
||||
{worldFacts.map((fact, i) => (
|
||||
<div
|
||||
key={fact.id || i}
|
||||
className="p-4 bg-muted/50 rounded-lg border border-border/50"
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-foreground leading-relaxed">{model.text}</p>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-sm text-foreground leading-relaxed flex-1">
|
||||
{fact.text}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs shrink-0"
|
||||
onClick={() => setViewMemoryId(fact.id)}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* No based_on data yet */}
|
||||
{!reflection.reflect_response && (
|
||||
<TabsContent value="experience">
|
||||
<div className="space-y-3">
|
||||
{experienceFacts.map((fact, i) => (
|
||||
<div
|
||||
key={fact.id || i}
|
||||
className="p-4 bg-muted/50 rounded-lg border border-border/50"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-sm text-foreground leading-relaxed flex-1">
|
||||
{fact.text}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs shrink-0"
|
||||
onClick={() => setViewMemoryId(fact.id)}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="mental-models">
|
||||
<div className="space-y-3">
|
||||
{mentalModels.map((model, i) => (
|
||||
<div
|
||||
key={model.id || i}
|
||||
className="p-4 bg-muted/50 rounded-lg border border-border/50"
|
||||
>
|
||||
<div className="flex items-start justify-between gap-2">
|
||||
<p className="text-sm text-foreground leading-relaxed flex-1">
|
||||
{model.text}
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 text-xs shrink-0"
|
||||
onClick={() => setViewMemoryId(model.id)}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
) : !reflection.reflect_response ? (
|
||||
<div className="border-t border-border pt-5">
|
||||
<div className="text-xs font-bold text-muted-foreground uppercase mb-3">Based On</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
@@ -784,7 +821,7 @@ function ReflectionDetailPanel({
|
||||
tracking.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
) : null}
|
||||
|
||||
{reflection.tags && reflection.tags.length > 0 && (
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user