jbot: disable local memory in Hermes Agent mode

In Hermes mode JBOT is a pure voice UI — memory is owned by the Agent:
- Backend: sys prompt excludes local memories when backend_mode=hermes
- Backend: config_view now returns backendMode so the frontend can adapt
- Frontend: App.jsx loads backendMode on mount via apiConfig()
- Frontend: memories not passed to apiChat() when isHermesMode
- Frontend: memory extraction (apiExtractMemory) skipped when isHermesMode
- Frontend: MemoryPanel hidden in Hermes mode; replaced by subtle
  '🧠 Hermes Agent 模式 · 记忆由 Agent 管理' badge

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-05-17 14:13:37 +10:00
co-authored by Copilot
parent 76603425a5
commit bb3fe1b554
5 changed files with 40 additions and 14 deletions
+3 -1
View File
@@ -547,7 +547,8 @@ def chat_view(request):
settings = data.get('settings', {})
mode = cfg.get('backend_mode', 'openrouter')
sys_content = _build_sys_prompt(settings, memories)
# In Hermes mode, don't inject local memories — Hermes manages its own memory
sys_content = _build_sys_prompt(settings, [] if mode == 'hermes' else memories)
try:
if mode == 'hermes':
@@ -626,4 +627,5 @@ def config_view(request):
'volcengine': bool(cfg['volc_app_id'] and cfg['volc_access_token']),
'llm': bool(cfg['openrouter_api_key']),
'ttsVoice': cfg['volc_tts_voice'],
'backendMode': cfg.get('backend_mode', 'openrouter'),
})
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
File diff suppressed because one or more lines are too long
+8
View File
@@ -663,6 +663,14 @@ kbd {
border-bottom: 1px solid var(--border);
overflow: hidden;
}
.hermes-mode-badge {
flex: 0 0 auto;
padding: 6px 14px;
font-size: 0.72rem;
color: var(--accent);
background: color-mix(in srgb, var(--accent) 10%, transparent);
border-bottom: 1px solid color-mix(in srgb, var(--accent) 30%, transparent);
}
.memory-toggle {
width: 100%;
display: flex;
+25 -9
View File
@@ -4,7 +4,7 @@ import RobotFace from './components/RobotFace';
import ChatPanel from './components/ChatPanel';
import MemoryPanel from './components/MemoryPanel';
import { EXPRESSIONS, ALL_AUTO_IDS, byId } from './data/expressions';
import { apiChat, apiTts, apiAsr, apiExtractMemory } from './services/api';
import { apiChat, apiTts, apiAsr, apiExtractMemory, apiConfig } from './services/api';
import { usePushToTalk } from './hooks/usePushToTalk';
import { addEntry } from './store/historyStore';
import { getMemories, addMemory } from './store/memoryStore';
@@ -134,11 +134,19 @@ export default function App() {
const [chatMessages, setChatMessages] = useState([]);
const [llmHistory, setLlmHistory] = useState([]); // last N {role,content} for context
const [memories, setMemories] = useState(() => getMemories());
const [isHermesMode, setIsHermesMode] = useState(false);
const voicePhaseRef = useRef('idle');
const isSpaceDownRef = useRef(false);
const refreshMemories = useCallback(() => setMemories(getMemories()), []);
// Load backend mode from server config on mount
useEffect(() => {
apiConfig().then(cfg => {
if (cfg?.backendMode === 'hermes') setIsHermesMode(true);
}).catch(() => {});
}, []);
useEffect(() => { voicePhaseRef.current = voicePhase; }, [voicePhase]);
const appendMsg = (role, text, audioBase64, audioMime, toolCalls) => {
@@ -220,7 +228,8 @@ export default function App() {
let botText = '';
let toolCalls = [];
try {
const currentMemories = getMemories().map(m => m.text);
// In Hermes mode don't pass local memories — Hermes owns its own memory
const currentMemories = isHermesMode ? [] : getMemories().map(m => m.text);
const currentSettings = getSettings();
const newHistory = [...llmHistory, { role: 'user', content: userText }];
const result = await apiChat(newHistory.slice(-10), currentMemories, currentSettings);
@@ -263,12 +272,14 @@ export default function App() {
botAudioMime: 'audio/mp3',
});
// Background memory extraction — never blocks the voice pipeline
apiExtractMemory(userText, botText).then(facts => {
let added = false;
facts.forEach(f => { if (addMemory(f)) added = true; });
if (added) refreshMemories();
}).catch(() => {});
// Background memory extraction — skip in Hermes mode (Hermes owns its memory)
if (!isHermesMode) {
apiExtractMemory(userText, botText).then(facts => {
let added = false;
facts.forEach(f => { if (addMemory(f)) added = true; });
if (added) refreshMemories();
}).catch(() => {});
}
// Play audio
if (botAudio) {
@@ -379,7 +390,12 @@ export default function App() {
{/* ── Right column: memory + chat + bottom bar ── */}
<div className="right-col">
<MemoryPanel memories={memories} onMemoriesChange={refreshMemories} />
{!isHermesMode && (
<MemoryPanel memories={memories} onMemoriesChange={refreshMemories} />
)}
{isHermesMode && (
<div className="hermes-mode-badge">🧠 Hermes Agent 模式 · 记忆由 Agent 管理</div>
)}
<ChatPanel messages={chatMessages} voiceStatus={voiceStatus} />
<VoiceButton phase={voicePhase} onStart={handleSpaceDown} onEnd={handleSpaceUp} />
</div>