Skip to content

Commit f5ce3e7

Browse files
committed
feat: integrate memory spaces and rolling review
1 parent 8ea86ec commit f5ce3e7

240 files changed

Lines changed: 21966 additions & 1593 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.gitignore‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,17 @@ extras/speaker-omni-experimental/cache/*
8787
# Discovery agent PID file
8888
edge/.discovery-agent.pid
8989

90+
# Runtime-generated diagnostics and reports. Keep durable documentation under docs/;
91+
# these locations are regenerated by local runs and screenshot/evaluation tooling.
92+
/artifacts/
93+
/backends/advanced/artifacts/
94+
95+
# Runtime and recovered Chronicle memory vaults. The canonical backend vault is
96+
# already covered by backends/advanced/data/* above; the recursive rule also keeps
97+
# copied vault trees from accidentally becoming Git-visible elsewhere.
98+
**/conversation_docs/
99+
/recovered-vault/
100+
90101
# Local Graphify knowledge graph. Agents query this directly; it is generated
91102
# per checkout and hidden from the editor explorer via .vscode/settings.json.
92103
graphify-out/

‎CONTEXT.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ objects. Capture and meaning are different layers.
3030
audio ranges are authoritative; related Conversation IDs are lineage/navigation.
3131
- **Vault**: the Markdown memory source of truth. It is derived from settled semantic
3232
inputs and can be regenerated from durable database evidence.
33+
- **Potential memory**: a proposed change derived from one reviewed day and the
34+
currently accepted Vault. It is not memory and is invisible to retrieval until a
35+
person accepts it.
36+
- **Review day**: one local day whose Episode account and Potential memories are being
37+
decided. Review days advance in chronological order.
38+
- **Unexplained evidence**: captured evidence inside a reviewed interval that no active
39+
Timeline episode accounts for. It is distinct from a capture gap, where no evidence
40+
exists to interpret.
3341

3442
## Invariants
3543

@@ -70,6 +78,9 @@ objects. Capture and meaning are different layers.
7078
conversation. Global filters, enrollment, memory, and corpus analysis must collapse
7179
unknowns into a category or key them by conversation plus local label; they must
7280
never equate matching placeholder text across conversations.
81+
17. A later Review day never observes an unresolved Potential memory. It is derived
82+
only after every earlier Review day has either changed the accepted Vault or been
83+
rejected without changing it.
7384

7485
## Important Modules and Interfaces
7586

‎backends/advanced/docker-compose.yml‎

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,22 +220,24 @@ services:
220220
# for HTTPS. compose-stack.md#webui-dev
221221
webui-dev:
222222
build:
223-
context: ./webui
224-
dockerfile: Dockerfile.dev
223+
context: ../..
224+
dockerfile: backends/advanced/webui/Dockerfile.dev
225225
ports:
226226
- "${WEBUI_DEV_PORT:-5173}:5173"
227227
environment:
228228
- VITE_BACKEND_URL=${VITE_BACKEND_URL-http://${HOST_IP}:${BACKEND_PUBLIC_PORT:-8000}}
229229
- VITE_ALLOWED_HOSTS=${VITE_ALLOWED_HOSTS:-localhost 127.0.0.1}
230230
- VITE_HMR_PORT=${VITE_HMR_PORT:-5173}
231231
volumes:
232-
- ./webui/src:/app/src
233-
- ./webui/public:/app/public
232+
- ./webui/src:/workspace/backends/advanced/webui/src
233+
- ../../contracts/audio/v2/typescript:/workspace/contracts/audio/v2/typescript:ro
234+
- ./webui/public:/workspace/backends/advanced/webui/public
234235
# Vite's entry document. Baked into the image otherwise, so edits to the
235236
# page title/meta silently kept serving the built-in copy until a rebuild.
236-
- ./webui/index.html:/app/index.html:ro
237-
- ./webui/tailwind.config.js:/app/tailwind.config.js:ro
238-
- ./webui/chronicle-espresso-preset.js:/app/chronicle-espresso-preset.js:ro
237+
- ./webui/index.html:/workspace/backends/advanced/webui/index.html:ro
238+
- ./webui/vite.config.ts:/workspace/backends/advanced/webui/vite.config.ts:ro
239+
- ./webui/tailwind.config.js:/workspace/backends/advanced/webui/tailwind.config.js:ro
240+
- ./webui/chronicle-espresso-preset.js:/workspace/backends/advanced/webui/chronicle-espresso-preset.js:ro
239241
depends_on:
240242
chronicle-backend:
241243
condition: service_healthy
@@ -287,6 +289,7 @@ services:
287289
volumes:
288290
- ./data/syncthing_config:/var/syncthing/config
289291
- ./data/conversation_docs:/vaults # backend exposes per-user folders under here
292+
- ./data/memory_spaces:/memory-spaces # isolated space vaults are sibling folders
290293
ports:
291294
- "22000:22000/tcp" # sync protocol (reachable via Tailscale)
292295
- "22000:22000/udp" # QUIC

‎backends/advanced/init.py‎

Lines changed: 37 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,6 @@
4848

4949
# Match the Pi executor's conservative fallbacks without baking one machine's
5050
# served context into every model selected by the wizard.
51-
DEFAULT_PI_CONTEXT_WINDOW = 32768
52-
DEFAULT_PI_MAX_TOKENS = 4096
53-
PI_PROMPT_HEADROOM_TOKENS = 1024
5451
MANAGED_LLAMACPP_REGISTRY_MODELS = {"llamacpp-llm", "muse-glimmer-llm"}
5552

5653

@@ -1349,10 +1346,6 @@ def setup_memory_agents(self):
13491346
if not isinstance(existing_pi, dict):
13501347
existing_pi = {}
13511348

1352-
configured_llm = str(
1353-
self.config_manager.get_config_defaults().get("llm") or "muse-glimmer-llm"
1354-
)
1355-
13561349
codex_config = {
13571350
"model": "gpt-5.6-luna",
13581351
"reasoning_effort": "low",
@@ -1363,60 +1356,51 @@ def setup_memory_agents(self):
13631356
**existing_codex,
13641357
}
13651358
pi_config = {
1366-
"model": configured_llm,
13671359
"timeout_seconds": 900,
13681360
"thinking": "off",
13691361
**existing_pi,
13701362
}
1371-
if not str(pi_config.get("model") or "").strip():
1372-
pi_config["model"] = configured_llm
1363+
# Model routing is owned by defaults + llm_operations. Older wizard runs
1364+
# copied that policy into the executor adapter, causing provider switches to
1365+
# require a second unrelated edit.
1366+
pi_config.pop("model", None)
13731367

13741368
if "pi" in (selected_write, selected_search, recovery_backend):
13751369
effective_models = self._effective_model_registry()
1376-
llm_models = sorted(
1377-
name
1378-
for name, model in effective_models.items()
1379-
if str(model.get("model_type") or "").lower() == "llm"
1380-
and str(model.get("api_family") or "").lower() == "openai"
1381-
)
1382-
if llm_models:
1383-
self.console.print(
1384-
"[blue][INFO][/blue] Available OpenAI-compatible LLM registry entries: "
1385-
+ ", ".join(llm_models)
1386-
)
1387-
pi_model = self.prompt_value(
1388-
"Chronicle model registry entry for Pi",
1389-
str(pi_config.get("model") or configured_llm),
1390-
).strip()
1391-
if not pi_model:
1392-
raise ValueError("Pi requires a Chronicle model registry entry")
1393-
selected_model = effective_models.get(pi_model)
1394-
if selected_model is None:
1395-
raise ValueError(
1396-
f"Pi model registry entry '{pi_model}' does not exist in the "
1397-
"effective defaults.yml + config.yml registry"
1398-
)
1399-
if str(selected_model.get("model_type") or "").lower() != "llm":
1400-
raise ValueError(f"Pi model registry entry '{pi_model}' is not an LLM")
1401-
if str(selected_model.get("api_family") or "").lower() != "openai":
1402-
raise ValueError(
1403-
f"Pi model registry entry '{pi_model}' is not OpenAI-compatible"
1404-
)
1405-
1406-
previous_model = str(existing_pi.get("model") or "").strip()
1407-
model_changed = bool(previous_model and previous_model != pi_model)
1408-
if "context_window" not in existing_pi or model_changed:
1409-
context_window = self._pi_context_window(selected_model)
1410-
pi_config["context_window"] = context_window
1411-
else:
1412-
context_window = int(pi_config["context_window"])
1413-
if "max_tokens" not in existing_pi or model_changed:
1414-
pi_config["max_tokens"] = self._pi_max_tokens(context_window)
1415-
pi_config["model"] = pi_model
1370+
full_config = self.config_manager.get_full_config()
1371+
defaults = full_config.get("defaults") or {}
1372+
operations = full_config.get("llm_operations") or {}
1373+
operation_names = set()
1374+
if "pi" in (selected_write, recovery_backend):
1375+
operation_names.add("memory_write")
1376+
if selected_search == "pi":
1377+
operation_names.add("memory_search")
1378+
resolved_names = []
1379+
for operation_name in sorted(operation_names):
1380+
operation_config = operations.get(operation_name) or {}
1381+
model_name = str(
1382+
operation_config.get("model") or defaults.get("llm") or ""
1383+
).strip()
1384+
selected_model = effective_models.get(model_name)
1385+
if selected_model is None:
1386+
raise ValueError(
1387+
f"{operation_name} resolves to model registry entry "
1388+
f"{model_name!r}, which does not exist"
1389+
)
1390+
if str(selected_model.get("model_type") or "").lower() != "llm":
1391+
raise ValueError(
1392+
f"{operation_name} resolves to {model_name!r}, which is not an LLM"
1393+
)
1394+
if str(selected_model.get("api_family") or "").lower() != "openai":
1395+
raise ValueError(
1396+
f"{operation_name} resolves to {model_name!r}, which is not "
1397+
"OpenAI-compatible"
1398+
)
1399+
resolved_names.append(f"{operation_name} → {model_name}")
14161400
self.console.print(
1417-
"[green][SUCCESS][/green] Pi will use model registry entry: "
1418-
f"{pi_model}. No Pi auth directory or host login is required; the "
1419-
"backend resolves the model URL/key and creates isolated runtime config."
1401+
"[green][SUCCESS][/green] Pi will use central model routing: "
1402+
+ ", ".join(resolved_names)
1403+
+ ". No Pi auth directory or host login is required."
14201404
)
14211405

14221406
if "codex" in (selected_write, recovery_backend):
@@ -1508,30 +1492,6 @@ def _effective_model_registry(self) -> Dict[str, Dict[str, Any]]:
15081492
effective[str(model["name"])] = dict(model)
15091493
return effective
15101494

1511-
@staticmethod
1512-
def _pi_context_window(model: Dict[str, Any]) -> int:
1513-
"""Use a model-declared context, falling back to Pi's safe generic window."""
1514-
model_params = model.get("model_params") or {}
1515-
raw = model.get("context_window")
1516-
if raw in (None, "") and isinstance(model_params, dict):
1517-
raw = model_params.get("context_window")
1518-
try:
1519-
context_window = int(raw)
1520-
except (TypeError, ValueError):
1521-
return DEFAULT_PI_CONTEXT_WINDOW
1522-
if context_window <= PI_PROMPT_HEADROOM_TOKENS:
1523-
return DEFAULT_PI_CONTEXT_WINDOW
1524-
return context_window
1525-
1526-
@staticmethod
1527-
def _pi_max_tokens(context_window: int) -> int:
1528-
"""Keep most context for prompts/tools while allowing larger-context models more output."""
1529-
return min(
1530-
DEFAULT_PI_MAX_TOKENS,
1531-
context_window // 4,
1532-
context_window - PI_PROMPT_HEADROOM_TOKENS,
1533-
)
1534-
15351495
def setup_optional_services(self):
15361496
"""Configure optional services"""
15371497
# Check if speaker service URL provided via args

‎backends/advanced/src/advanced_omi_backend/app_factory.py‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@
5151
)
5252
from advanced_omi_backend.models.manual_memory import ManualMemory
5353
from advanced_omi_backend.models.memory_audit import MemoryAuditEntry
54+
from advanced_omi_backend.models.memory_space import (
55+
DeferredSpaceEvent,
56+
MemorySpace,
57+
SpaceMergeProposal,
58+
)
5459
from advanced_omi_backend.models.system_event import SystemEvent
5560
from advanced_omi_backend.models.timeline import (
5661
AudioEvidenceSpan,
@@ -170,6 +175,9 @@ async def lifespan(app: FastAPI):
170175
Annotation,
171176
MemoryAuditEntry,
172177
ManualMemory,
178+
MemorySpace,
179+
SpaceMergeProposal,
180+
DeferredSpaceEvent,
173181
SystemEvent,
174182
CaptureSource,
175183
PairingCode,

0 commit comments

Comments
 (0)