Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
32ee8cd
feat(gui): Added handoff llm call log details to gui
sortedcord Jul 18, 2026
821f6a0
refactor(gui): Merge master into fix/handoff
sortedcord Jul 18, 2026
c6b32bb
Merge branch 'master' into fix/handoff
sortedcord Jul 18, 2026
b4bf70d
refactor(memory,gui): Streamline memory archtiecture and terminology
sortedcord Jul 18, 2026
1ed1edf
refactor!(memory): Streamline memory terminology and architecture
sortedcord Jul 19, 2026
0512be6
feat(intent): Add additional "thought" intent type
sortedcord Jul 19, 2026
84bff92
refactor(intent): Improve intent decoder userContext structure
sortedcord Jul 19, 2026
a4b6205
FEAT!(voice): Implement intent hydration, dehydration system fixes: #29
sortedcord Jul 19, 2026
5c3a79e
fix(voice): Quote splitting based on single and double quotes
sortedcord Jul 19, 2026
c292626
refactor(content): Updated talking room to add more coherent memory
sortedcord Jul 19, 2026
f8977a1
refactor(gui): Use structured logging over string parsing
NeoLi00 Jul 19, 2026
1e34bec
refactor(gui): Unify prompt analysis components for actor, intent dec…
NeoLi00 Jul 19, 2026
ee25bf4
refactor(llm, memory): Use generic types prompt builder and prompt co…
sortedcord Jul 19, 2026
7baf583
refactor(architect): Use IPromptBuilder Interface for LLMValidator
sortedcord Jul 19, 2026
8ff1650
feat(gui): Model Statistics now show Validators in the pipeline
sortedcord Jul 19, 2026
01ec062
fix(gui): Add hydration fallback in gui
sortedcord Jul 19, 2026
8f6fe0d
chore: fix linting issues
NeoLi00 Jul 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Access the application locally at `http://localhost:3000`.

### The Actor Agent

Each entity takes turns through an **Actor Agent** that receives a strictly epistemically bounded prompt: its own attributes (public, plus private ones explicitly granted to itself), its subjective memory buffer, the entities co-present at its location, and the current moment. Nothing else. The actor responds with free narrative prose.
Each entity takes turns through an **Actor Agent** that receives a strictly epistemically bounded prompt: its own attributes (public, plus private ones explicitly granted to itself), its **Cognitive Buffer**, the entities co-present at its location, and the current moment. Nothing else. The actor responds with free narrative prose.

Prose is decoded into typed intents:

Expand Down Expand Up @@ -121,8 +121,8 @@ Space is a graph: `world → region → location → point of interest`, connect

### Memory Tiers

- **Verbatim Buffer (implemented):** Per-character subjective event log. Every entry is stored from the owner's perspective actors resolved through the owner's alias map, outcomes attached — and recalled with naturalized time phrasing.
- **Vector Archive (implemented):** Summarized, embedded memory entries for semantic retrieval, keeping verbatim quotes only for high-salience lines.
- **Cognitive Buffer (implemented):** Per-character subjective event log. Every entry is stored from the owner's perspective actors resolved through the owner's alias map, outcomes attached — and recalled with naturalized time phrasing.
- **Memory Ledger (implemented):** Summarized, embedded memory entries for semantic retrieval, keeping verbatim quotes only for high-salience lines.
- **Dossier (planned):** Each observer's subjective beliefs about another character.

Memory is per-character on purpose: recall is testimony from a vantage point, which is what makes interrogating two witnesses interesting.
Expand Down Expand Up @@ -156,14 +156,14 @@ The finish line for the first milestone is small on purpose. `v0` is almost on t
- [x] Typed intent pipeline: `dialogue` / `action` / `monologue`, decoded from free prose.
- [x] World Architect: LLM validation plus time-delta generation, end-to-end for single actions.
- [x] Actor Agent with epistemically-bounded prompts (self, memory, co-located entities, subjective time).
- [x] Verbatim memory buffer with per-observer subjective serialization and alias resolution.
- [x] Verbatim Cognitive Buffer with per-observer subjective serialization and alias resolution.
- [x] Spatial location graph (data model; perception is co-location only).
- [x] Scenario loader (JSON → SQLite) and a playable CLI loop with human or LLM actors.

**[The `v0` Milestone:](https://github.com/sortedcord/omnia-consolidated/milestone/1)**

- [x] Two hand-authored NPCs live in one location, playable via CLI.
- [x] Each has buffer and vector-archive memory and recalls something said a few turns earlier.
- [x] Each has Cognitive Buffer and Memory Ledger memory and recalls something said a few turns earlier.
- [x] One NPC knows a fact the other does not and, provably by testing, will not leak it.
- [x] The Architect processes at least one non-trivial action per exchange with a visible state change.
- [x] The whole thing persists to a SQLite file and reloads identically.
Expand All @@ -183,7 +183,7 @@ omnia/
intent/ intent types (dialogue/action/monologue) and the prose decoder
architect/ World Architect: LLM validation plus time-delta generation
actor/ actor agent: epistemically-bounded prompts, pluggable prose generators
memory/ verbatim buffer; later the vector archive, dossier, and affect vectors
memory/ Cognitive Buffer; Memory Ledger (vector archive), dossier, and affect vectors
spatial/ location and POI graph, portal-based perception
llm/ ILLMProvider interface plus Gemini and deterministic mock implementations
scenario/ scenario JSON schema and loader (JSON → SQLite)
Expand Down
2 changes: 1 addition & 1 deletion apps/gui/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
import "./.next/dev/types/routes.d.ts";

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
1 change: 1 addition & 0 deletions apps/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@omnia/memory": "workspace:*",
"@omnia/scenario": "workspace:*",
"@omnia/spatial": "workspace:*",
"@omnia/voice": "workspace:*",
"@radix-ui/react-dialog": "^1.1.19",
"@radix-ui/react-separator": "^1.1.11",
"@radix-ui/react-slot": "^1.3.0",
Expand Down
4 changes: 2 additions & 2 deletions apps/gui/src/components/config/ConfigView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,13 @@ export function ConfigView() {
{
key: "handoff",
label: "Memory Handoff Engine",
desc: "Promotes entities' working memories to the long-term Ledger via LLM summarization and pruning.",
desc: "Promotes entities' Cognitive Buffer entries to the Memory Ledger via LLM summarization and pruning.",
type: "generative",
},
{
key: "embeddings",
label: "Text Embeddings Generator",
desc: "Generates vector embeddings for long-term memory retrieval.",
desc: "Generates vector embeddings for Memory Ledger retrieval.",
type: "embedding",
},
].map((task) => (
Expand Down
10 changes: 1 addition & 9 deletions apps/gui/src/components/config/ProviderInstancesConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,7 @@ import {
CardTitle,
CardAction,
} from "@/components/ui/card";
import {
Item,
ItemContent,
ItemGroup,
ItemTitle,
ItemDescription,
} from "@/components/ui/item";
import { Item, ItemContent, ItemGroup, ItemTitle } from "@/components/ui/item";
import { Empty, EmptyTitle, EmptyDescription } from "@/components/ui/empty";
import { cn } from "@/lib/utils";
import { RefreshCwIcon } from "lucide-react";
Expand Down Expand Up @@ -200,14 +194,12 @@ export function ProviderInstancesConfig({
fetchModelsForExistingInstance(selectedInstanceId);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedInstanceId, instances, availableProviders]);

// Re-fetch models when provider/key/endpoint changes on new instance form
useEffect(() => {
if (selectedInstanceId !== "new") return;
fetchModelsForNewInstance(editProvider, editKey, editEndpointUrl);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editProvider, editKey, editEndpointUrl, selectedInstanceId]);

const handleProviderChange = (providerId: string | null) => {
Expand Down
254 changes: 254 additions & 0 deletions apps/gui/src/components/play/HandoffModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
"use client";

import { useState } from "react";
import type { SimSnapshot } from "@/lib/simulation-types";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog";
import { Badge } from "@/components/ui/badge";
import { PromptAnalyzer } from "@/components/play/PromptAnalyzer";

interface HandoffModalProps {
entry: SimSnapshot["log"][number];
onClose: () => void;
}

export function HandoffModal({ entry, onClose }: HandoffModalProps) {
const [activeTab, setActiveTab] = useState<"chunks" | "prompt" | "output">(
"chunks",
);

const handoffResult = entry.handoffResult;
const chunks = handoffResult?.chunks || [];

const getImportanceColor = (score: number) => {
if (score >= 8)
return "bg-destructive/10 text-destructive border-destructive/30";
if (score >= 5) return "bg-amber-500/10 text-amber-500 border-amber-500/30";
return "bg-emerald-500/10 text-emerald-500 border-emerald-500/30";
};

return (
<Dialog open onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-[800px] sm:max-w-[800px] h-[85vh] overflow-hidden flex flex-col p-0 gap-0 border-2">
<DialogHeader className="px-6 pt-5 pb-4 border-b">
<DialogTitle className="text-lg font-head tracking-wide text-primary flex items-center justify-between">
<span>Memory Handoff Details &mdash; {entry.entityName}</span>
{entry.usage && (
<span className="text-xs font-mono font-normal text-muted-foreground">
{entry.usage.modelName || "Handoff Model"}
</span>
)}
</DialogTitle>
</DialogHeader>

{/* Custom Tab Switcher */}
<div className="flex border-b bg-muted/20 px-6 py-2 gap-2">
<button
onClick={() => setActiveTab("chunks")}
className={`px-3 py-1.5 text-xs font-medium border transition-all duration-100 ${
activeTab === "chunks"
? "border-primary bg-primary/10 text-primary shadow-[1px_1px_0_0_var(--primary)]"
: "border-transparent hover:bg-secondary text-muted-foreground"
}`}
>
Promoted Chunks ({chunks.length})
</button>
<button
onClick={() => setActiveTab("prompt")}
className={`px-3 py-1.5 text-xs font-medium border transition-all duration-100 ${
activeTab === "prompt"
? "border-primary bg-primary/10 text-primary shadow-[1px_1px_0_0_var(--primary)]"
: "border-transparent hover:bg-secondary text-muted-foreground"
}`}
>
Raw LLM Prompt
</button>
<button
onClick={() => setActiveTab("output")}
className={`px-3 py-1.5 text-xs font-medium border transition-all duration-100 ${
activeTab === "output"
? "border-primary bg-primary/10 text-primary shadow-[1px_1px_0_0_var(--primary)]"
: "border-transparent hover:bg-secondary text-muted-foreground"
}`}
>
Raw JSON Output
</button>
</div>

<div className="overflow-y-auto flex-1 p-6 space-y-4">
{activeTab === "chunks" && (
<div className="space-y-4">
{entry.usage && (
<div className="grid grid-cols-3 gap-4 border border-dotted border-border/20 p-3 bg-secondary/10 rounded text-xs font-mono">
<div>
<span className="text-muted-foreground block uppercase tracking-wider text-[10px]">
Input Tokens
</span>
<strong className="text-foreground">
{entry.usage.inputTokens}
</strong>
</div>
<div>
<span className="text-muted-foreground block uppercase tracking-wider text-[10px]">
Output Tokens
</span>
<strong className="text-foreground">
{entry.usage.outputTokens}
</strong>
</div>
<div>
<span className="text-muted-foreground block uppercase tracking-wider text-[10px]">
Total Tokens
</span>
<strong className="text-foreground">
{entry.usage.totalTokens}
</strong>
</div>
</div>
)}

{chunks.length === 0 ? (
<div className="border border-dotted border-border/30 p-8 text-center bg-card text-muted-foreground rounded">
<p className="text-sm">
No memories were promoted to the Memory Ledger during this
turn.
</p>
<p className="text-xs mt-1">
All Cognitive Buffer entries were summarized or forgotten.
</p>
</div>
) : (
<div className="space-y-4">
<h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground font-mono">
Memory Ledger Additions
</h3>
{chunks.map(
(
chunk: { content: string; importance: number },
index: number,
) => (
<div
key={index}
className="border border-border/30 bg-card p-4 shadow-sm relative flex flex-col gap-3"
>
<div className="flex justify-between items-start gap-4">
<div className="flex-1 text-sm text-foreground/90 leading-relaxed font-sans">
{chunk.content}
</div>
<Badge
variant="outline"
className={`font-mono text-xs ${getImportanceColor(chunk.importance)}`}
>
Importance: {chunk.importance}
</Badge>
</div>

{chunk.quotes && chunk.quotes.length > 0 && (
<div className="bg-secondary/10 border-l-2 border-primary/50 p-2.5 my-1 text-xs italic text-muted-foreground space-y-1">
{chunk.quotes.map((quote: string, qIdx: number) => (
<div key={qIdx}>&ldquo;{quote}&rdquo;</div>
))}
</div>
)}

<div className="flex flex-wrap gap-2 text-xs pt-2 border-t border-dotted border-border/10">
{chunk.retainInBuffer ? (
<Badge
variant="outline"
className="bg-primary/5 text-primary border-primary/20 text-[10px] font-mono"
>
Pinned in Buffer
</Badge>
) : (
<Badge
variant="outline"
className="bg-muted text-muted-foreground border-border/20 text-[10px] font-mono"
>
Pruned from Buffer
</Badge>
)}

{chunk.involvedEntityIds &&
chunk.involvedEntityIds.length > 0 && (
<div className="flex items-center gap-1.5 ml-auto text-[10px] font-mono text-muted-foreground">
<span>Entities:</span>
{chunk.involvedEntityIds.map(
(entId: string) => (
<Badge
key={entId}
variant="outline"
className="text-[10px] px-1 py-0 border-border/20 font-mono"
>
{entId}
</Badge>
),
)}
</div>
)}
</div>
</div>
),
)}
</div>
)}
</div>
)}

{activeTab === "prompt" && entry.rawPrompt && (
<PromptAnalyzer
components={
entry.rawPrompt.components &&
entry.rawPrompt.components.length > 0
? entry.rawPrompt.components
: [
{
label: "System Prompt",
type: "system",
content: entry.rawPrompt.systemPrompt || "",
},
{
label: "User Context",
type: "world",
content: entry.rawPrompt.userContext || "",
},
]
}
inputTokens={entry.usage?.inputTokens || 0}
maxContext={
entry.usage?.maxContext !== undefined
? entry.usage.maxContext
: 32768
}
modelName={entry.usage?.modelName}
providerInstanceName={entry.usage?.providerInstanceName}
outputLabel="LLM Output (Promoted Memory Chunks)"
outputText={
handoffResult
? JSON.stringify(handoffResult, null, 2)
: undefined
}
outputTokens={entry.usage?.outputTokens}
/>
)}

{activeTab === "output" && (
<div className="space-y-2 h-full flex flex-col">
<h4 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground font-mono">
Raw JSON Output
</h4>
<pre className="p-3 bg-muted rounded text-xs font-mono whitespace-pre-wrap text-foreground border flex-1 overflow-y-auto max-h-[500px]">
{handoffResult
? JSON.stringify(handoffResult, null, 2)
: "No JSON Output recorded."}
</pre>
</div>
)}
</div>
</DialogContent>
</Dialog>
);
}
Loading
Loading