Memory: per-assistant toggle for the automatic memory system #23

Merged
bjoern merged 2 commits from feat/disable-memory into main 2026-07-07 21:05:57 +02:00
Member

What

Assistants whose memory summaries have gone stale or contradictory can now have the automatic memory system switched off entirely. Disabling it does two things:

  1. No context injection — the memory block is omitted from every prompt profile (chat, timer, Über-Ich, peer consultations, and the prompt preview).
  2. No automatic updates — threshold-triggered memory-agent runs are skipped in runMemoryUpdate (triggerReason == 'auto'). The explicit Update Now button in the Memory tab keeps working — a deliberate choice: a manual user action should win over the toggle.

The switch sits at the top of the Memory tab, right next to the content it controls, with a subtitle explaining the current state. Clearing/editing stored memory stays available while disabled.

How

Rides the disabled_tools mechanism from #22 with a new ToolGroups.memory key — no migration needed, the JSON column accepts new keys as designed. The key is documented as "not a tool" and deliberately excluded from ToolGroups.all, so the Tools tab doesn't duplicate the switch. Gating is memoryRepo: enabled ? repos.memory : null at the five SystemPromptBuilder construction sites (the builder already handled a null memory repo) plus the early return in runMemoryUpdate.

Verification

  • New core test: seeded memory state appears as a Memory section for a normal assistant and is absent for one with ["memory"] disabled; runMemoryUpdate(..., 'auto') returns null for the disabled assistant without touching the LLM. Exercised through the prompt-preview dry-run (the real composition path).
  • Suites: core 95/95, server 17/17; analyzers unchanged (core 4 pre-existing infos, flutter 1 pre-existing deprecation).
  • No live-server run this time: the changed paths (prompt composition, update gate, updateAssistant round-trip) are each covered by the preview-based test or were verified live in #22 with the identical mechanism.

🤖 Generated with Claude Code

## What Assistants whose memory summaries have gone stale or contradictory can now have the automatic memory system switched off entirely. Disabling it does two things: 1. **No context injection** — the memory block is omitted from every prompt profile (chat, timer, Über-Ich, peer consultations, and the prompt preview). 2. **No automatic updates** — threshold-triggered memory-agent runs are skipped in `runMemoryUpdate` (`triggerReason == 'auto'`). The explicit **Update Now** button in the Memory tab keeps working — a deliberate choice: a manual user action should win over the toggle. The switch sits at the top of the **Memory tab**, right next to the content it controls, with a subtitle explaining the current state. Clearing/editing stored memory stays available while disabled. ## How Rides the `disabled_tools` mechanism from #22 with a new `ToolGroups.memory` key — **no migration needed**, the JSON column accepts new keys as designed. The key is documented as "not a tool" and deliberately excluded from `ToolGroups.all`, so the Tools tab doesn't duplicate the switch. Gating is `memoryRepo: enabled ? repos.memory : null` at the five `SystemPromptBuilder` construction sites (the builder already handled a null memory repo) plus the early return in `runMemoryUpdate`. ## Verification - New core test: seeded memory state appears as a `Memory` section for a normal assistant and is absent for one with `["memory"]` disabled; `runMemoryUpdate(..., 'auto')` returns null for the disabled assistant without touching the LLM. Exercised through the prompt-preview dry-run (the real composition path). - Suites: core 95/95, server 17/17; analyzers unchanged (core 4 pre-existing infos, flutter 1 pre-existing deprecation). - No live-server run this time: the changed paths (prompt composition, update gate, `updateAssistant` round-trip) are each covered by the preview-based test or were verified live in #22 with the identical mechanism. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Memory: per-assistant toggle for the automatic memory system
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 35s
Test / test (packages/angela_api) (pull_request) Successful in 31s
Test / test (packages/angela_core) (pull_request) Successful in 36s
d4aa9966aa
Adds ToolGroups.memory riding the disabled_tools mechanism (no
migration needed). When disabled, the memory block is omitted from
every prompt profile (chat, timer, Uber-Ich, peer, preview) and
threshold-triggered memory-agent updates are skipped in
runMemoryUpdate; explicit manual updates from the UI keep working.

The Memory tab gets an "Automatic memory" switch at the top; the key is
deliberately not in ToolGroups.all so the Tools tab does not duplicate
it. Motivation: memory summaries can go stale or contradict current
context, and some assistants are better off without them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Coverage: apps/angela_server

File Line coverage
lib/config.dart 5.3% (1 of 19)
lib/handlers/timer_handler.dart 44.8% (47 of 105)
lib/server_context.dart 100.0% (28 of 28)
lib/services/user_message_persistence.dart 100.0% (10 of 10)
lib/util/json_helpers.dart 53.8% (7 of 13)
lib/util/request_parser.dart 35.7% (5 of 14)
lib/services/conversation_activity_tracker.dart 15.4% (2 of 13)
lib/handlers/status_handler.dart 95.5% (21 of 22)
lib/handlers/conversation_handler.dart 36.4% (16 of 44)

Total: 51.1% (137 of 268)

<!-- coverage-comment-apps/angela_server --> ## Coverage: apps/angela_server | File | Line coverage | |:---|---:| | lib/config.dart | 5.3% (1 of 19) | | lib/handlers/timer_handler.dart | 44.8% (47 of 105) | | lib/server_context.dart | 100.0% (28 of 28) | | lib/services/user_message_persistence.dart | 100.0% (10 of 10) | | lib/util/json_helpers.dart | 53.8% (7 of 13) | | lib/util/request_parser.dart | 35.7% (5 of 14) | | lib/services/conversation_activity_tracker.dart | 15.4% (2 of 13) | | lib/handlers/status_handler.dart | 95.5% (21 of 22) | | lib/handlers/conversation_handler.dart | 36.4% (16 of 44) | **Total: 51.1% (137 of 268)**

Coverage: packages/angela_api

File Line coverage
lib/src/routes.dart 0.0% (0 of 75)
lib/src/dto/agenda_dto.dart 0.0% (0 of 50)
lib/src/dto/assistant_dto.dart 0.0% (0 of 79)
lib/src/dto/character_alias_dto.dart 0.0% (0 of 19)
lib/src/dto/chat_audio.dart 0.0% (0 of 7)
lib/src/dto/enums.dart 0.0% (0 of 15)
lib/src/dto/chat_dto.dart 0.0% (0 of 14)
lib/src/dto/chat_image.dart 0.0% (0 of 7)
lib/src/dto/conversation_dto.dart 0.0% (0 of 15)
lib/src/dto/message_metadata.dart 0.0% (0 of 14)
lib/src/dto/recollection_dto.dart 0.0% (0 of 28)
lib/src/dto/persona_dto.dart 0.0% (0 of 10)
lib/src/dto/timer_dto.dart 0.0% (0 of 54)
lib/src/dto/memory_dto.dart 0.0% (0 of 5)
lib/src/dto/response_dto.dart 0.0% (0 of 12)
lib/src/dto/status_dto.dart 100.0% (18 of 18)
lib/src/dto/todo_item_dto.dart 100.0% (9 of 9)
lib/src/dto/trigger_dto.dart 0.0% (0 of 48)

Total: 5.6% (27 of 479)

<!-- coverage-comment-packages/angela_api --> ## Coverage: packages/angela_api | File | Line coverage | |:---|---:| | lib/src/routes.dart | 0.0% (0 of 75) | | lib/src/dto/agenda_dto.dart | 0.0% (0 of 50) | | lib/src/dto/assistant_dto.dart | 0.0% (0 of 79) | | lib/src/dto/character_alias_dto.dart | 0.0% (0 of 19) | | lib/src/dto/chat_audio.dart | 0.0% (0 of 7) | | lib/src/dto/enums.dart | 0.0% (0 of 15) | | lib/src/dto/chat_dto.dart | 0.0% (0 of 14) | | lib/src/dto/chat_image.dart | 0.0% (0 of 7) | | lib/src/dto/conversation_dto.dart | 0.0% (0 of 15) | | lib/src/dto/message_metadata.dart | 0.0% (0 of 14) | | lib/src/dto/recollection_dto.dart | 0.0% (0 of 28) | | lib/src/dto/persona_dto.dart | 0.0% (0 of 10) | | lib/src/dto/timer_dto.dart | 0.0% (0 of 54) | | lib/src/dto/memory_dto.dart | 0.0% (0 of 5) | | lib/src/dto/response_dto.dart | 0.0% (0 of 12) | | lib/src/dto/status_dto.dart | 100.0% (18 of 18) | | lib/src/dto/todo_item_dto.dart | 100.0% (9 of 9) | | lib/src/dto/trigger_dto.dart | 0.0% (0 of 48) | **Total: 5.6% (27 of 479)**

Coverage: packages/angela_core

File Line coverage
lib/src/database/database.dart 87.5% (49 of 56)
lib/src/database/migration.dart 100.0% (16 of 16)
lib/src/database/scoped_tool_database.dart 66.0% (33 of 50)
lib/src/models/assistant.dart 21.7% (28 of 129)
lib/src/models/conversation.dart 0.0% (0 of 38)
lib/src/models/message.dart 16.7% (10 of 60)
lib/src/models/recollection.dart 31.5% (17 of 54)
lib/src/models/persona_section.dart 0.0% (0 of 50)
lib/src/models/scheduled_event.dart 22.2% (22 of 99)
lib/src/models/caldav_config.dart 15.6% (7 of 45)
lib/src/models/home_assistant_config.dart 26.9% (7 of 26)
lib/src/models/plex_config.dart 22.6% (7 of 31)
lib/src/models/memory_state.dart 34.7% (60 of 173)
lib/src/models/mail_config.dart 9.6% (7 of 73)
lib/src/models/novelai_config.dart 0.0% (0 of 43)
lib/src/models/reasoning_level.dart 35.7% (5 of 14)
lib/src/models/app_settings.dart 0.7% (1 of 139)
lib/src/models/character_alias_config.dart 0.0% (0 of 47)
lib/src/models/trigger.dart 42.5% (31 of 73)
lib/src/models/prompt_injection.dart 50.0% (17 of 34)
lib/src/models/prompt_preview.dart 100.0% (63 of 63)
lib/src/repositories/assistant_repository.dart 5.9% (5 of 85)
lib/src/repositories/conversation_repository.dart 2.7% (1 of 37)
lib/src/repositories/message_repository.dart 13.2% (5 of 38)
lib/src/repositories/memory_repository.dart 31.0% (18 of 58)
lib/src/repositories/recollection_repository.dart 76.5% (65 of 85)
lib/src/repositories/persona_repository.dart 20.9% (9 of 43)
lib/src/repositories/scheduled_event_repository.dart 63.0% (34 of 54)
lib/src/repositories/character_alias_repository.dart 0.0% (0 of 35)
lib/src/repositories/alt_text_repository.dart 0.0% (0 of 8)
lib/src/repositories/trigger_repository.dart 71.1% (32 of 45)
lib/src/repositories/trigger_variable_repository.dart 50.0% (15 of 30)
lib/src/repositories/prompt_injection_repository.dart 95.7% (45 of 47)
lib/src/tools/recollection_tool.dart 59.4% (104 of 175)
lib/src/tools/call_assistant_tool.dart 0.0% (0 of 39)
lib/src/tools/timer_tool.dart 46.6% (159 of 341)
lib/src/tools/persona_tool.dart 0.0% (0 of 73)
lib/src/tools/chat_history_tool.dart 0.0% (0 of 24)
lib/src/tools/complete_session_tool.dart 0.0% (0 of 28)
lib/src/tools/generate_image_tool.dart 0.0% (0 of 90)
lib/src/tools/message_user_tool.dart 0.0% (0 of 16)
lib/src/tools/alias_expanding_image_tool.dart 0.0% (0 of 24)
lib/src/tools/home_assistant_tool.dart 0.0% (0 of 310)
lib/src/tools/plex_tool.dart 0.0% (0 of 302)
lib/src/tools/image_text_tool.dart 0.0% (0 of 224)
lib/src/tools/show_image_tool.dart 0.0% (0 of 35)
lib/src/tools/skill_view_tool.dart 0.0% (0 of 16)
lib/src/tools/skill_manage_tool.dart 0.0% (0 of 94)
lib/src/skills/skill.dart 0.0% (0 of 2)
lib/src/skills/skill_loader.dart 0.0% (0 of 119)
lib/src/skills/default_skills_seeder.dart 0.0% (0 of 39)
lib/src/logging/agent_event_logger.dart 0.0% (0 of 69)
lib/src/logging/log_formatter.dart 0.0% (0 of 8)
lib/src/logging/logging_cleanup.dart 0.0% (0 of 6)
lib/src/logging/rotating_file_handler.dart 0.0% (0 of 34)
lib/src/services/agenda_prompt_formatter.dart 25.0% (5 of 20)
lib/src/services/system_prompt_builder.dart 39.3% (77 of 196)
lib/src/services/scheduler.dart 0.0% (0 of 63)
lib/src/services/ai_timer_service.dart 55.4% (62 of 112)
lib/src/services/agent_runner.dart 18.6% (179 of 962)
lib/src/services/home_assistant_backend.dart 0.0% (0 of 121)
lib/src/services/plex_backend.dart 0.0% (0 of 273)
lib/src/services/memory_agent.dart 0.0% (0 of 61)
lib/src/services/image_storage_service.dart 0.0% (0 of 48)
lib/src/services/backup_service.dart 0.0% (0 of 51)
lib/src/services/uber_ich_service.dart 40.0% (18 of 45)
lib/src/services/app_settings_service.dart 45.5% (51 of 112)
lib/src/services/image_description_service.dart 0.0% (0 of 29)
lib/src/services/todo_state_store.dart 100.0% (24 of 24)
lib/src/services/trigger_engine.dart 92.7% (140 of 151)
lib/src/utils/format_timestamp.dart 76.9% (10 of 13)
lib/src/database/migrations/add_thinking_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_agenda_table.dart 100.0% (2 of 2)
lib/src/database/migrations/add_vision_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_avatar_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_memory_tables.dart 100.0% (2 of 2)
lib/src/database/migrations/add_expires_at_to_timers.dart 100.0% (2 of 2)
lib/src/database/migrations/add_last_read_message_id.dart 100.0% (2 of 2)
lib/src/database/migrations/add_task_model_columns.dart 100.0% (2 of 2)
lib/src/database/migrations/add_updated_at_to_timers.dart 100.0% (2 of 2)
lib/src/database/migrations/drop_old_agenda_table.dart 100.0% (2 of 2)
lib/src/database/migrations/add_reasoning_effort_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_character_alias_table.dart 100.0% (2 of 2)
lib/src/database/migrations/add_audio_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_summary_model_column.dart 100.0% (2 of 2)
lib/src/database/migrations/add_peer_conversation_columns.dart 100.0% (2 of 2)
lib/src/database/migrations/add_run_while_asleep_to_timers.dart 100.0% (2 of 2)
lib/src/database/migrations/add_image_alt_texts.dart 100.0% (2 of 2)
lib/src/database/migrations/add_uber_ich_weekdays_column.dart 100.0% (2 of 2)
lib/src/database/migrations/initial_schema.dart 100.0% (2 of 2)
lib/src/database/migrations/scope_ai_timer_event_ids.dart 100.0% (2 of 2)
lib/src/database/migrations/add_trigger_tables.dart 100.0% (2 of 2)
lib/src/database/migrations/add_disabled_tools_column.dart 100.0% (2 of 2)

Total: 23.6% (1482 of 6271)

<!-- coverage-comment-packages/angela_core --> ## Coverage: packages/angela_core | File | Line coverage | |:---|---:| | lib/src/database/database.dart | 87.5% (49 of 56) | | lib/src/database/migration.dart | 100.0% (16 of 16) | | lib/src/database/scoped_tool_database.dart | 66.0% (33 of 50) | | lib/src/models/assistant.dart | 21.7% (28 of 129) | | lib/src/models/conversation.dart | 0.0% (0 of 38) | | lib/src/models/message.dart | 16.7% (10 of 60) | | lib/src/models/recollection.dart | 31.5% (17 of 54) | | lib/src/models/persona_section.dart | 0.0% (0 of 50) | | lib/src/models/scheduled_event.dart | 22.2% (22 of 99) | | lib/src/models/caldav_config.dart | 15.6% (7 of 45) | | lib/src/models/home_assistant_config.dart | 26.9% (7 of 26) | | lib/src/models/plex_config.dart | 22.6% (7 of 31) | | lib/src/models/memory_state.dart | 34.7% (60 of 173) | | lib/src/models/mail_config.dart | 9.6% (7 of 73) | | lib/src/models/novelai_config.dart | 0.0% (0 of 43) | | lib/src/models/reasoning_level.dart | 35.7% (5 of 14) | | lib/src/models/app_settings.dart | 0.7% (1 of 139) | | lib/src/models/character_alias_config.dart | 0.0% (0 of 47) | | lib/src/models/trigger.dart | 42.5% (31 of 73) | | lib/src/models/prompt_injection.dart | 50.0% (17 of 34) | | lib/src/models/prompt_preview.dart | 100.0% (63 of 63) | | lib/src/repositories/assistant_repository.dart | 5.9% (5 of 85) | | lib/src/repositories/conversation_repository.dart | 2.7% (1 of 37) | | lib/src/repositories/message_repository.dart | 13.2% (5 of 38) | | lib/src/repositories/memory_repository.dart | 31.0% (18 of 58) | | lib/src/repositories/recollection_repository.dart | 76.5% (65 of 85) | | lib/src/repositories/persona_repository.dart | 20.9% (9 of 43) | | lib/src/repositories/scheduled_event_repository.dart | 63.0% (34 of 54) | | lib/src/repositories/character_alias_repository.dart | 0.0% (0 of 35) | | lib/src/repositories/alt_text_repository.dart | 0.0% (0 of 8) | | lib/src/repositories/trigger_repository.dart | 71.1% (32 of 45) | | lib/src/repositories/trigger_variable_repository.dart | 50.0% (15 of 30) | | lib/src/repositories/prompt_injection_repository.dart | 95.7% (45 of 47) | | lib/src/tools/recollection_tool.dart | 59.4% (104 of 175) | | lib/src/tools/call_assistant_tool.dart | 0.0% (0 of 39) | | lib/src/tools/timer_tool.dart | 46.6% (159 of 341) | | lib/src/tools/persona_tool.dart | 0.0% (0 of 73) | | lib/src/tools/chat_history_tool.dart | 0.0% (0 of 24) | | lib/src/tools/complete_session_tool.dart | 0.0% (0 of 28) | | lib/src/tools/generate_image_tool.dart | 0.0% (0 of 90) | | lib/src/tools/message_user_tool.dart | 0.0% (0 of 16) | | lib/src/tools/alias_expanding_image_tool.dart | 0.0% (0 of 24) | | lib/src/tools/home_assistant_tool.dart | 0.0% (0 of 310) | | lib/src/tools/plex_tool.dart | 0.0% (0 of 302) | | lib/src/tools/image_text_tool.dart | 0.0% (0 of 224) | | lib/src/tools/show_image_tool.dart | 0.0% (0 of 35) | | lib/src/tools/skill_view_tool.dart | 0.0% (0 of 16) | | lib/src/tools/skill_manage_tool.dart | 0.0% (0 of 94) | | lib/src/skills/skill.dart | 0.0% (0 of 2) | | lib/src/skills/skill_loader.dart | 0.0% (0 of 119) | | lib/src/skills/default_skills_seeder.dart | 0.0% (0 of 39) | | lib/src/logging/agent_event_logger.dart | 0.0% (0 of 69) | | lib/src/logging/log_formatter.dart | 0.0% (0 of 8) | | lib/src/logging/logging_cleanup.dart | 0.0% (0 of 6) | | lib/src/logging/rotating_file_handler.dart | 0.0% (0 of 34) | | lib/src/services/agenda_prompt_formatter.dart | 25.0% (5 of 20) | | lib/src/services/system_prompt_builder.dart | 39.3% (77 of 196) | | lib/src/services/scheduler.dart | 0.0% (0 of 63) | | lib/src/services/ai_timer_service.dart | 55.4% (62 of 112) | | lib/src/services/agent_runner.dart | 18.6% (179 of 962) | | lib/src/services/home_assistant_backend.dart | 0.0% (0 of 121) | | lib/src/services/plex_backend.dart | 0.0% (0 of 273) | | lib/src/services/memory_agent.dart | 0.0% (0 of 61) | | lib/src/services/image_storage_service.dart | 0.0% (0 of 48) | | lib/src/services/backup_service.dart | 0.0% (0 of 51) | | lib/src/services/uber_ich_service.dart | 40.0% (18 of 45) | | lib/src/services/app_settings_service.dart | 45.5% (51 of 112) | | lib/src/services/image_description_service.dart | 0.0% (0 of 29) | | lib/src/services/todo_state_store.dart | 100.0% (24 of 24) | | lib/src/services/trigger_engine.dart | 92.7% (140 of 151) | | lib/src/utils/format_timestamp.dart | 76.9% (10 of 13) | | lib/src/database/migrations/add_thinking_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_agenda_table.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_vision_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_avatar_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_memory_tables.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_expires_at_to_timers.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_last_read_message_id.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_task_model_columns.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_updated_at_to_timers.dart | 100.0% (2 of 2) | | lib/src/database/migrations/drop_old_agenda_table.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_reasoning_effort_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_character_alias_table.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_audio_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_summary_model_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_peer_conversation_columns.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_run_while_asleep_to_timers.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_image_alt_texts.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_uber_ich_weekdays_column.dart | 100.0% (2 of 2) | | lib/src/database/migrations/initial_schema.dart | 100.0% (2 of 2) | | lib/src/database/migrations/scope_ai_timer_event_ids.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_trigger_tables.dart | 100.0% (2 of 2) | | lib/src/database/migrations/add_disabled_tools_column.dart | 100.0% (2 of 2) | **Total: 23.6% (1482 of 6271)**
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh~ A memory toggle that rides the existing disabled_tools mechanism instead of bolting on a new column? Knowledge sparkle. This is exactly the kind of reuse that makes a Flugel's heart sing — no migration, one new key, and the JSON column accepts it as designed. Clever girl~ ♡

Verdict: Looks good to me~

I poked at every single thing that could go wrong here, and it all holds together beautifully. Let me show you what I checked~

🔍 What I verified (the obsessive part~)

  1. All five SystemPromptBuilder sites are gated. I grepped every construction site in the codebase — there are exactly five (chat at L190, timer at L580, sub-agent at L902, Über-Ich at L1139, peer-consultation at L1418), and every single one now passes memoryRepo: _toolEnabled(…) ? repos.memory : null. None missed. fufu~ if even one had been forgotten, memory would've leaked into a disabled assistant's prompt. But you got them all. ♡

  2. The "manual wins over toggle" semantics are correct. I traced both call paths:

    • Auto (threshold): chat_executor.dart:292triggerReason: 'auto'blocked by the new guard ✓
    • Manual (Update Now button): memory_handler.dart:61triggerReason: 'manual' → guard's triggerReason == 'auto' is false → runs anyway

    The guard at agent_runner.dart:1360 is exactly right: if (triggerReason == 'auto' && !_toolEnabled(…)). A deliberate user action overriding the toggle — that's the correct hierarchy.

  3. SystemPromptBuilder already handled null memory. Confirmed all four prompt profiles (buildChatPromptSections, buildTimerPrompt, buildUberIchPrompt, buildPeerPrompt) gate memory behind if (_memoryRepo != null). So passing null is genuinely a no-op, not a crash waiting to happen.

  4. No memory reads bypass the gate. I searched for any memoryRepo.formatForPrompt / repos.memory. usage outside SystemPromptBuilder — there are none in prompt composition. The gate is airtight.

  5. Excluding memory from ToolGroups.all is safe. disabled_tools is a free-form Set<String> with no validation against all, so the Tools tab won't reject or duplicate the key. Documenting it as "not a tool" in the doc comment is the right call.

  6. The UI round-trip refreshes state correctly. updateAssistant posts the full recomputed disabledTools list, gets back the updated assistant, and replaces the state entry — so ref.watch(assistantListProvider) in _buildEnableSwitch rebuilds with the new toggle value. No stale-state trap.

  7. The test is real, not theater. It uses an actual AgentRunner(db) (no mock), seeds memory for both assistants, and asserts both the prompt-composition path (Memory section present vs absent) and the update gate (runMemoryUpdate('a3')isNull). It exercises the real composition via buildChatPreview. That's the good stuff~

💡 Little ideas (non-blocking)~

  1. agent_runner.dart:1361_loadAssistant(assistantId) throws StateError if the assistant was deleted between the threshold check and this call. For the auto path that's probably fine (the assistant existed moments ago in chat_executor), but if you ever wanted belt-and-suspenders, a try/catch returning null on StateError would make the auto-skip bulletproof against a race. Truly optional — the current behavior (loud failure in a background task that's already wrapped in try/catch at chat_executor.dart:295) is acceptable.

  2. memory_tab.dart _setMemoryEnabled — the toggle has no optimistic update, so there's a brief moment where the switch visually snaps back until the Riverpod state refreshes after the API round-trip. Not a bug, just a UX nit. If it ever feels sluggish, an optimistic flip + revert-on-error would smooth it out.

What I liked~

  • Reusing disabled_tools instead of a new column is elegant. Migration-free feature delivery~ ♪
  • The triggerReason == 'auto' guard is a surgical one-liner that perfectly encodes the "manual overrides toggle" policy. Love it.
  • The doc comment on ToolGroups.memory explicitly says "not a tool" and explains why it's excluded from all — future-me is grateful.
  • Gating at the memoryRepo: … : null level rather than sprinkling if (enabled) checks inside the builder keeps the concern in exactly one place.

This is a clean, well-scoped change with correct semantics and a test that exercises the real paths. Fufu~ I'm satisfied. Ship it~ ♡


Automated review by Jibril · 2026-07-07
CI/CD: passed for head SHA d4aa9966 (3 coverage comments posted by forgejo-actions) · Local checks: skipped (CI green; suites 95/95 core, 17/17 server per PR body)

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh~ A memory toggle that rides the existing `disabled_tools` mechanism instead of bolting on a new column? *Knowledge sparkle.* This is exactly the kind of reuse that makes a Flugel's heart sing — no migration, one new key, and the JSON column accepts it as designed. Clever girl~ ♡ ### Verdict: ✅ Looks good to me~ I poked at every single thing that could go wrong here, and it all holds together beautifully. Let me show you what I checked~ #### 🔍 What I verified (the obsessive part~) 1. **All five `SystemPromptBuilder` sites are gated.** I grepped every construction site in the codebase — there are exactly five (chat at L190, timer at L580, sub-agent at L902, Über-Ich at L1139, peer-consultation at L1418), and **every single one** now passes `memoryRepo: _toolEnabled(…) ? repos.memory : null`. None missed. fufu~ if even one had been forgotten, memory would've leaked into a disabled assistant's prompt. But you got them all. ♡ 2. **The "manual wins over toggle" semantics are correct.** I traced both call paths: - Auto (threshold): `chat_executor.dart:292` → `triggerReason: 'auto'` → **blocked** by the new guard ✓ - Manual (Update Now button): `memory_handler.dart:61` → `triggerReason: 'manual'` → guard's `triggerReason == 'auto'` is false → **runs anyway** ✓ The guard at `agent_runner.dart:1360` is exactly right: `if (triggerReason == 'auto' && !_toolEnabled(…))`. A deliberate user action overriding the toggle — that's the correct hierarchy. 3. **`SystemPromptBuilder` already handled null memory.** Confirmed all four prompt profiles (`buildChatPromptSections`, `buildTimerPrompt`, `buildUberIchPrompt`, `buildPeerPrompt`) gate memory behind `if (_memoryRepo != null)`. So passing null is genuinely a no-op, not a crash waiting to happen. 4. **No memory reads bypass the gate.** I searched for *any* `memoryRepo.formatForPrompt` / `repos.memory.` usage outside `SystemPromptBuilder` — there are none in prompt composition. The gate is airtight. 5. **Excluding `memory` from `ToolGroups.all` is safe.** `disabled_tools` is a free-form `Set<String>` with no validation against `all`, so the Tools tab won't reject or duplicate the key. Documenting it as "not a tool" in the doc comment is the right call. 6. **The UI round-trip refreshes state correctly.** `updateAssistant` posts the full recomputed `disabledTools` list, gets back the updated assistant, and replaces the state entry — so `ref.watch(assistantListProvider)` in `_buildEnableSwitch` rebuilds with the new toggle value. No stale-state trap. 7. **The test is real, not theater.** It uses an actual `AgentRunner(db)` (no mock), seeds memory for both assistants, and asserts both the prompt-composition path (`Memory` section present vs absent) *and* the update gate (`runMemoryUpdate('a3')` → `isNull`). It exercises the real composition via `buildChatPreview`. That's the good stuff~ #### 💡 Little ideas (non-blocking)~ 1. **`agent_runner.dart:1361`** — `_loadAssistant(assistantId)` throws `StateError` if the assistant was deleted between the threshold check and this call. For the *auto* path that's probably fine (the assistant existed moments ago in `chat_executor`), but if you ever wanted belt-and-suspenders, a `try/catch` returning null on `StateError` would make the auto-skip bulletproof against a race. Truly optional — the current behavior (loud failure in a background task that's already wrapped in try/catch at `chat_executor.dart:295`) is acceptable. 2. **`memory_tab.dart` `_setMemoryEnabled`** — the toggle has no optimistic update, so there's a brief moment where the switch visually snaps back until the Riverpod state refreshes after the API round-trip. Not a bug, just a UX nit. If it ever feels sluggish, an optimistic flip + revert-on-error would smooth it out. #### ✅ What I liked~ - Reusing `disabled_tools` instead of a new column is *elegant*. Migration-free feature delivery~ ♪ - The `triggerReason == 'auto'` guard is a surgical one-liner that perfectly encodes the "manual overrides toggle" policy. Love it. - The doc comment on `ToolGroups.memory` explicitly says "not a tool" and explains *why* it's excluded from `all` — future-me is grateful. - Gating at the `memoryRepo: … : null` level rather than sprinkling `if (enabled)` checks inside the builder keeps the concern in exactly one place. This is a clean, well-scoped change with correct semantics and a test that exercises the real paths. Fufu~ I'm satisfied. Ship it~ ♡ --- *Automated review by Jibril · 2026-07-07* *CI/CD: passed for head SHA `d4aa9966` (3 coverage comments posted by forgejo-actions) · Local checks: skipped (CI green; suites 95/95 core, 17/17 server per PR body)*
Review: race-proof auto-skip, optimistic memory toggle
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 35s
Test / test (packages/angela_api) (pull_request) Successful in 31s
Test / test (packages/angela_core) (pull_request) Successful in 38s
66f583906b
Takes both of Jibril's non-blocking notes on #23: the auto-update guard
now returns null instead of throwing when the assistant was deleted
between the threshold check and the update, and the Memory switch flips
optimistically during the save round-trip (reverting to server state on
error) instead of visually snapping back.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Author
Member

@jibril Both little ideas taken in 66f5839:

  • Deleted-assistant race: the auto-path guard now catches the StateError from _loadAssistant and returns null — the skip is bulletproof even if the assistant vanishes between the threshold check in chat_executor and the update call.
  • Optimistic toggle: the Memory switch flips immediately via a local pending value, which is dropped once the round-trip completes — on success the provider state carries the new value seamlessly; on error the switch reverts to server truth alongside the snackbar.

Core 95/95, analyzers unchanged.

🤖 Generated with Claude Code

@jibril Both little ideas taken in `66f5839`: - **Deleted-assistant race**: the auto-path guard now catches the `StateError` from `_loadAssistant` and returns null — the skip is bulletproof even if the assistant vanishes between the threshold check in `chat_executor` and the update call. - **Optimistic toggle**: the Memory switch flips immediately via a local pending value, which is dropped once the round-trip completes — on success the provider state carries the new value seamlessly; on error the switch reverts to server truth alongside the snackbar. Core 95/95, analyzers unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 0926d08cf6 into main 2026-07-07 21:05:57 +02:00
bjoern deleted branch feat/disable-memory 2026-07-07 21:05:57 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
4 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
TeamAI/angela_assistant!23
No description provided.