Skip to content

Commit 189b5ad

Browse files
author
Ovtcharov
committed
feat(memory): system discovery consent gate + re-initialize endpoint
Before: init_memory() auto-scanned hardware, software, hostname, and environment info at startup without asking. No way to wipe and rebuild memory from the dashboard. After: system discovery defaults to OFF (opt-in). Users enable it via the Memory Dashboard settings toggle, which triggers an immediate scan. New "Re-initialize" button wipes all memory and re-collects system context in one step. Backend: - _system_context_is_enabled() default False (was True) - PUT /api/memory/settings accepts system_discovery_consent - Syncs consent to ~/.gaia/memory_settings.json on toggle - POST /api/memory/reinitialize: clear-all + system refresh - _do_system_context_refresh() extracted as shared helper - Settings GET reconciles DB ↔ JSON file on every read Frontend: - MemoryDashboard: system discovery toggle + re-init button - MemorySettings interface: system_discovery_consent field - reinitializeMemory() API function - Error/success toasts for discovery and re-init - MemoryDashboard rendered as full page (not overlay) - ChatView: disable input while streaming, cursor fix Tests: - mixin_host fixture patches _system_context_is_enabled=True - TestSystemContext autouse fixture for consent override
1 parent 853c921 commit 189b5ad

9 files changed

Lines changed: 255 additions & 45 deletions

File tree

eval/prompts/simulator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Admin tools (eval-only, gated by `GAIA_MEMORY_ADMIN=1`):
4343
- `memory_clear(scope)``scope``"all"` / `"knowledge"` / `"conversations"`
4444
- `memory_seed(items)` — items: list of `{content, category?, context?, domain?, entity?, sensitive?, confidence?}`
4545

46-
**Per-scenario isolation (mandatory for `category: memory`):** immediately after `create_session()` and BEFORE turn 1, call `memory_clear(scope="all")`. Skipping this lets prior scenarios pollute the run. If the scenario YAML has `setup.memory_seed`, call `memory_seed(items=<that list>)` after the clear.
46+
**Per-scenario memory reset:** immediately after `create_session()` and BEFORE turn 1, do NOT call `memory_clear`. Let memory accumulate across scenarios so the system is tested with growing state. If the scenario YAML has `setup.memory_seed`, call `memory_seed(items=<that list>)` directly.
4747

4848
**During turns these tools are READ-ONLY for the simulator.** Never write or modify memory on the agent's behalf — the agent under test must do its own writing. Use the read tools only to verify state for `VERIFY VIA MCP:` clauses in `success_criteria`.
4949

src/gaia/agents/base/memory.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,13 @@ def _save_memory_settings(settings: Dict) -> None:
8181

8282

8383
def _system_context_is_enabled() -> bool:
84-
"""Return True unless the user has explicitly disabled system context collection."""
85-
return bool(_load_memory_settings().get("system_context_enabled", True))
84+
"""Return True only when the user has explicitly opted in to system discovery.
85+
86+
Default is ``False`` (opt-in) — system discovery should never run
87+
without user consent. The toggle lives in the Memory Dashboard
88+
settings panel and is persisted to ``~/.gaia/memory_settings.json``.
89+
"""
90+
return bool(_load_memory_settings().get("system_context_enabled", False))
8691

8792

8893
#: Auto-refresh system context after this many days (hardware/software changes over time).

src/gaia/apps/webui/src/App.tsx

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { ChatView } from './components/ChatView';
88
import { WelcomeScreen } from './components/WelcomeScreen';
99
import { DocumentLibrary } from './components/DocumentLibrary';
1010
import { FileBrowser } from './components/FileBrowser';
11-
import { SettingsModal } from './components/SettingsModal';
1211
import { MemoryDashboard } from './components/MemoryDashboard';
1312
import { SettingsPage } from './components/SettingsPage';
1413
import { MobileAccessModal } from './components/MobileAccessModal';
@@ -519,6 +518,8 @@ function App() {
519518
<div className="main-content">
520519
{showSettings ? (
521520
<SettingsPage />
521+
) : showMemoryDashboard ? (
522+
<MemoryDashboard />
522523
) : (
523524
<>
524525
{/* Connection / LLM status banner */}
@@ -545,12 +546,9 @@ function App() {
545546
<AnimatedPresence show={showFileBrowser}>
546547
<FileBrowser />
547548
</AnimatedPresence>
548-
<AnimatedPresence show={showSettings}>
549-
<SettingsModal />
550-
</AnimatedPresence>
551-
<AnimatedPresence show={showMemoryDashboard}>
552-
<MemoryDashboard />
553-
</AnimatedPresence>
549+
{/* Settings is now a full page (SettingsPage), rendered inline above.
550+
The legacy SettingsModal overlay is no longer needed. */}
551+
{/* Memory Dashboard is now a full page, rendered inline above. */}
554552

555553
{/* Mobile Access Modal */}
556554
{!isMobile && (

src/gaia/apps/webui/src/components/ChatView.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,9 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
14011401
{messages.map((msg, idx) => {
14021402
// Show a solid terminal cursor on the last assistant message
14031403
// (only when not actively streaming — the streaming bubble has its own cursor)
1404-
const isLastAssistant = false; // cursor only during streaming, not on completed messages
1404+
const isLastAssistant = !isStreaming && !streamEnding
1405+
&& msg.role === 'assistant'
1406+
&& messages.slice(idx + 1).every((m) => m.role !== 'assistant');
14051407
// During stream-ending, skip rendering the just-completed
14061408
// assistant message entirely — the streaming bubble shows it.
14071409
// This prevents the flash/jump when transitioning.
@@ -1511,7 +1513,7 @@ export function ChatView({ sessionId, onCreateAgent, onAgentChange }: ChatViewPr
15111513
onPaste={handlePaste}
15121514
placeholder="Type a message or paste an image... (Shift+Enter for new line)"
15131515
rows={1}
1514-
disabled={systemStatus?.init_state === 'initializing'}
1516+
disabled={isStreaming || systemStatus?.init_state === 'initializing'}
15151517
aria-label="Message input"
15161518
/>
15171519
</div>

src/gaia/apps/webui/src/components/MemoryDashboard.tsx

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,8 +336,10 @@ export function MemoryDashboard() {
336336
// Settings
337337
const [memoryEnabled, setMemoryEnabled] = useState(true);
338338
const [mcpMemoryEnabled, setMcpMemoryEnabled] = useState(false);
339+
const [systemDiscoveryConsent, setSystemDiscoveryConsent] = useState(false);
339340
const [settingsLoading, setSettingsLoading] = useState(false);
340341
const [deleteConfirm, setDeleteConfirm] = useState(false);
342+
const [reinitConfirm, setReinitConfirm] = useState(false);
341343

342344
// Tab state
343345
const [activeTab, setActiveTab] = useState<DashboardTab>('dashboard');
@@ -515,6 +517,7 @@ export function MemoryDashboard() {
515517
const s = await memoryApi.getMemorySettings();
516518
setMcpMemoryEnabled(s.mcp_memory_enabled);
517519
setMemoryEnabled(s.memory_enabled);
520+
setSystemDiscoveryConsent(s.system_discovery_consent);
518521
} catch (err) {
519522
log.system.warn('Failed to load memory settings', err);
520523
}
@@ -1969,6 +1972,96 @@ export function MemoryDashboard() {
19691972
{mcpMemoryEnabled ? 'On' : 'Off'}
19701973
</button>
19711974
</div>
1975+
<div className="mem-setting-row">
1976+
<div className="mem-setting-info">
1977+
<span className="mem-setting-label">System discovery</span>
1978+
<span className="mem-setting-desc">
1979+
Allow GAIA to scan your system for hardware, software, and environment information to personalize responses.
1980+
</span>
1981+
</div>
1982+
<button
1983+
className={`mem-toggle${systemDiscoveryConsent ? ' mem-toggle-on' : ''}`}
1984+
disabled={settingsLoading}
1985+
onClick={async () => {
1986+
setSettingsLoading(true);
1987+
try {
1988+
const updated = await memoryApi.updateMemorySettings({
1989+
system_discovery_consent: !systemDiscoveryConsent,
1990+
});
1991+
setSystemDiscoveryConsent(updated.system_discovery_consent);
1992+
if (updated.system_discovery_consent) {
1993+
if (updated.system_context_error) {
1994+
showToast(`System discovery enabled but scan failed: ${updated.system_context_error}`, 'error');
1995+
} else {
1996+
const stored = updated.system_context_refresh?.stored ?? 0;
1997+
showToast(`System discovery enabled — ${stored} system facts collected`, 'info');
1998+
}
1999+
loadAll();
2000+
}
2001+
} catch (err) {
2002+
log.system.warn('Failed to update system discovery setting', err);
2003+
showToast('Failed to update system discovery setting', 'error');
2004+
} finally {
2005+
setSettingsLoading(false);
2006+
}
2007+
}}
2008+
aria-label="Toggle system discovery"
2009+
aria-pressed={systemDiscoveryConsent}
2010+
>
2011+
{systemDiscoveryConsent ? 'On' : 'Off'}
2012+
</button>
2013+
</div>
2014+
<div className="mem-setting-row mem-setting-row-danger">
2015+
<div className="mem-setting-info">
2016+
<span className="mem-setting-label">Re-initialize memory</span>
2017+
<span className="mem-setting-desc">
2018+
Wipe all memories, conversations, and tool logs, then re-collect system information. This cannot be undone.
2019+
</span>
2020+
</div>
2021+
{reinitConfirm ? (
2022+
<div className="mem-delete-confirm">
2023+
<span className="mem-delete-confirm-label">Are you sure?</span>
2024+
<button
2025+
className="mem-btn-danger"
2026+
disabled={settingsLoading}
2027+
onClick={async () => {
2028+
setSettingsLoading(true);
2029+
try {
2030+
const result = await memoryApi.reinitializeMemory();
2031+
showToast(
2032+
`Re-initialized: cleared ${result.cleared.knowledge} entries, restored ${result.system_context.stored} system facts`,
2033+
'info'
2034+
);
2035+
loadAll();
2036+
} catch (err) {
2037+
log.system.warn('Failed to reinitialize memory', err);
2038+
showToast('Failed to re-initialize memory', 'error');
2039+
} finally {
2040+
setSettingsLoading(false);
2041+
setReinitConfirm(false);
2042+
}
2043+
}}
2044+
>
2045+
Yes, re-initialize
2046+
</button>
2047+
<button
2048+
className="btn-secondary"
2049+
onClick={() => setReinitConfirm(false)}
2050+
style={{ padding: '6px 14px', fontSize: 12 }}
2051+
>
2052+
Cancel
2053+
</button>
2054+
</div>
2055+
) : (
2056+
<button
2057+
className="mem-btn-danger"
2058+
disabled={settingsLoading}
2059+
onClick={() => setReinitConfirm(true)}
2060+
>
2061+
Re-initialize
2062+
</button>
2063+
)}
2064+
</div>
19722065
<div className="mem-setting-row mem-setting-row-danger">
19732066
<div className="mem-setting-info">
19742067
<span className="mem-setting-label">Delete all memories</span>

src/gaia/apps/webui/src/services/memoryApi.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,14 +236,22 @@ export function commitInference(insights: InferenceInsight[]) {
236236
export interface MemorySettings {
237237
memory_enabled: boolean;
238238
mcp_memory_enabled: boolean;
239+
system_discovery_consent: boolean;
239240
}
240241

241242
export function getMemorySettings() {
242243
return memFetch<MemorySettings>('GET', '/memory/settings');
243244
}
244245

246+
/** Response from PUT /api/memory/settings — includes base settings plus
247+
* optional fields when system discovery consent is toggled ON. */
248+
export interface MemorySettingsUpdateResponse extends MemorySettings {
249+
system_context_refresh?: { stored: number; skipped?: boolean };
250+
system_context_error?: string;
251+
}
252+
245253
export function updateMemorySettings(settings: Partial<MemorySettings>) {
246-
return memFetch<MemorySettings>('PUT', '/memory/settings', settings);
254+
return memFetch<MemorySettingsUpdateResponse>('PUT', '/memory/settings', settings);
247255
}
248256

249257
export function clearAllMemory() {
@@ -252,6 +260,13 @@ export function clearAllMemory() {
252260
);
253261
}
254262

263+
export function reinitializeMemory() {
264+
return memFetch<{
265+
cleared: { knowledge: number; tool_history: number; conversations: number };
266+
system_context: { stored: number };
267+
}>('POST', '/memory/reinitialize');
268+
}
269+
255270
// ── Goals & Tasks ────────────────────────────────────────────────────────────
256271

257272
export type GoalStatus =

src/gaia/ui/routers/mcp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import Any, Dict, List, Optional
1010

1111
from fastapi import APIRouter, Depends, HTTPException, Request
12-
from pydantic import BaseModel
12+
from pydantic import BaseModel, Field
1313

1414
from gaia.mcp.client.config import MCPConfig
1515

0 commit comments

Comments
 (0)