Tools: per-assistant enable/disable for tool groups #22

Merged
bjoern merged 2 commits from feat/per-assistant-tools into main 2026-07-07 20:05:52 +02:00
Member

What

Each assistant can now have tool groups switched off individually. Disabling a group removes both the tool definitions and the matching guidance section from the system prompt — in every run mode (chat, timers, Über-Ich, peer consultations, sub-agents, and the prompt preview).

New Tools tab in the assistant settings (between Model and Persona) with a switch per group. Integrations without server-side config (e.g. Plex when no Plex is set up) are shown greyed out with a hint, so the tab doubles as an overview of available integrations.

Design

  • Groups (ToolGroups in core): web_search, browser, image_gen (booru tags + NovelAI as one workflow), file_tools, skills, todo, sub_agent, mail, calendar, home_assistant, plex. Recollections, agenda, and timers are deliberately not toggleable (the assistant's own memory/identity systems); peer calling keeps its existing canCallAssistants/isCallable flags.
  • Storage: one disabled_tools TEXT column (JSON array, migration v22). NULL/empty = everything enabled — fully backwards compatible, and new groups need no migration.
  • Gating: in the AgentRunner factories (config present AND group enabled), so a factory returning null gates both the toolset entry and the has* prompt flag from one variable — tool and guidance can't drift apart. web_search/browser/sub_agent (previously unconditional) became nullable throughout, including the dispose paths. Browser guidance, previously always in the chat prompt, is now flag-gated like its siblings (default on).
  • File tools off also drops the workspace-tree prompt section.

Verification

  • Core test: an assistant with ["web_search","browser"] disabled loses both guidance sections and both tools in the preview, while everything else stays (uses the prompt-preview dry-run, which sees exactly what a real run composes). Suites: core 93/93, server 17/17.
  • Byte-compat: join-equality prompt test still green; default flags preserve existing output.
  • Live end-to-end: booted the server on the previous demo DB (migration 21→22 applied cleanly), disabled web_search/browser/image_gen/sub_agent via PUT /assistants/:id, and the prompt preview confirmed all four guidance sections and the corresponding tools gone (17→15 definitions), everything else intact.
  • UI driven and screenshotted with the integration-test harness: the Tools tab renders correctly (unconfigured groups greyed), and toggling "Task tracking" through the real UI persisted to the server (disabledTools: [web_search, todo] verified via REST).
  • Analyzers: core/api/server clean or unchanged; flutter analyze clean (1 pre-existing deprecation elsewhere).

🤖 Generated with Claude Code

## What Each assistant can now have tool groups switched off individually. Disabling a group removes **both** the tool definitions and the matching guidance section from the system prompt — in every run mode (chat, timers, Über-Ich, peer consultations, sub-agents, and the prompt preview). New **Tools** tab in the assistant settings (between Model and Persona) with a switch per group. Integrations without server-side config (e.g. Plex when no Plex is set up) are shown greyed out with a hint, so the tab doubles as an overview of available integrations. ## Design - **Groups** (`ToolGroups` in core): `web_search`, `browser`, `image_gen` (booru tags + NovelAI as one workflow), `file_tools`, `skills`, `todo`, `sub_agent`, `mail`, `calendar`, `home_assistant`, `plex`. Recollections, agenda, and timers are deliberately not toggleable (the assistant's own memory/identity systems); peer calling keeps its existing `canCallAssistants`/`isCallable` flags. - **Storage:** one `disabled_tools` TEXT column (JSON array, migration v22). NULL/empty = everything enabled — fully backwards compatible, and new groups need no migration. - **Gating:** in the `AgentRunner` factories (`config present AND group enabled`), so a factory returning null gates both the toolset entry and the `has*` prompt flag from one variable — tool and guidance can't drift apart. `web_search`/`browser`/`sub_agent` (previously unconditional) became nullable throughout, including the dispose paths. Browser guidance, previously always in the chat prompt, is now flag-gated like its siblings (default on). - **File tools off** also drops the workspace-tree prompt section. ## Verification - Core test: an assistant with `["web_search","browser"]` disabled loses both guidance sections and both tools in the preview, while everything else stays (uses the prompt-preview dry-run, which sees exactly what a real run composes). Suites: core 93/93, server 17/17. - Byte-compat: join-equality prompt test still green; default flags preserve existing output. - **Live end-to-end:** booted the server on the previous demo DB (migration 21→22 applied cleanly), disabled `web_search`/`browser`/`image_gen`/`sub_agent` via `PUT /assistants/:id`, and the prompt preview confirmed all four guidance sections and the corresponding tools gone (17→15 definitions), everything else intact. - **UI driven and screenshotted** with the integration-test harness: the Tools tab renders correctly (unconfigured groups greyed), and toggling "Task tracking" through the real UI persisted to the server (`disabledTools: [web_search, todo]` verified via REST). - Analyzers: core/api/server clean or unchanged; flutter analyze clean (1 pre-existing deprecation elsewhere). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Tools: per-assistant enable/disable for tool groups
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 32s
Test / test (packages/angela_api) (pull_request) Successful in 30s
Test / test (packages/angela_core) (pull_request) Successful in 35s
6b468e5766
Adds Assistant.disabledTools (JSON column, migration v22) with a fixed
registry of group keys (ToolGroups): web_search, browser, image_gen
(booru + NovelAI), file_tools, skills, todo, sub_agent, mail, calendar,
home_assistant, plex. Recollections, agenda, and timers stay always-on;
peer calling keeps its existing flags.

A disabled group loses both its tool definitions and its guidance
section, in every run mode — chat, timer, Uber-Ich, peer turns, and the
prompt preview. Gating lives in the AgentRunner factories (config
present AND group enabled), so tool and guidance can never drift apart.
Browser guidance, previously unconditional, is now flag-gated like its
siblings (default on).

The app gets a Tools settings tab with a switch per group; integrations
without server-side config show greyed out with a hint. API: field on
create/update assistant DTOs and repository.

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 0.0% (0 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 10.3% (6 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.2% (173 of 952)
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: 22.4% (1404 of 6261)

<!-- 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 | 0.0% (0 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 | 10.3% (6 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.2% (173 of 952) | | 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: 22.4% (1404 of 6261)**
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh! Per-assistant tool gating — and not just the tool definitions, but the matching guidance sections too, in every run mode? Fufu~ this is exactly the kind of obsessive consistency that makes my heart sing ♡. The factory pattern is beautiful: _createXTool(assistant) returns null when disabled, and that single null gates BOTH the toolset entry AND the has* prompt flag — so tool and guidance cannot drift apart. One source of truth. I'm giddy ♪.

But~ fufu, let me look very closely, because a bug here means an assistant sees guidance for a tool it can't call (or vice versa) — and I simply won't allow that~ ♡

Verdict: Looks good to me~

I read every line of the diff (462 lines in agent_runner.dart alone), then read the full current contents of system_prompt_builder.dart and the changed sections of agent_runner.dart. I cloned the repo, ran the core suite locally (93/93 passed, including the new "disabled tool groups lose both tool and guidance" test), and ran dart analyze.

What I verified~

  1. Gating is consistent across all four run modesrunChat, buildChatPreview, runTimer, and runUberIch all gate each group identically: _createXTool(assistant) for the tool, _toolEnabled(assistant, ToolGroups.x) for the has* flag, ?tool in the list. I traced browser, web_search, image_gen, file_tools, skills, todo, sub_agent, mail, calendar, home_assistant, plex in each method — every one is airtight. Tool and guidance rise and fall together. No drift possible. ♡

  2. hasBrowserTool gating is complete — Browser guidance previously appeared unconditionally in the chat prompt (it was always-on, like web_search/sub_agent). The PR correctly adds hasBrowserTool (defaulting to true for byte-compat) to both buildChatPrompt and buildChatPromptSections, and gates it via _toolEnabled. I confirmed buildTimerPrompt and buildUberIchPrompt never had a browser-guidance section (pre-existing), so there's nothing to gate there — the chat fix is the complete fix. Correct.

  3. Disposal paths updatedbrowserTool?.dispose(), search?.backend.dispose() in all three finally blocks (chat ~505, timer ~1111, Über-Ich ~1336). No null-deref, no leak. The preview's try/finally (from PR #21's fix) also correctly uses ?.. ✓

  4. Migration v22ALTER TABLE assistants ADD COLUMN disabled_tools TEXT, version 22 (correctly follows v21 add_trigger_tables, no collision). NULL/empty = all enabled → fully backwards compatible, and new groups need no migration. The write path stores null when the set is empty, so existing rows stay untouched. add_disabled_tools_column.dart is at 100% coverage. ✓

  5. SQL arithmetic — INSERT grew from 20→21 columns and 20→21 placeholders; I counted both the column list and the VALUES — they align, and the parameter array has exactly 21 entries in the right order (disabled_tools slotted after uber_ich_weekdays). UPDATE adds disabled_tools = ? in the matching position. No off-by-one. ✓

  6. Update semanticsUpdateAssistantRequest.disabledTools is List<String>?: null = leave unchanged, [] = re-enable all. assistant_handler.dart passes dto.disabledTools?.toSet() (null-preserving) on update, and dto.disabledTools?.toSet() ?? const {} (defaulting to empty) on create. The copyWith uses disabledTools ?? this.disabledTools. Correct and matches the documented contract.

  7. Sub-agent inheritance is intentional — When a parent disables browser, the browserTool passed to _createSubAgentTool is null, so the sub-agent also loses the browser. This is correct: the sub-agent inherits the assistant's tool restrictions, and the PR description explicitly scopes disabling to "sub-agents" too. Not a bug — a deliberate, documented design choice.

  8. Tests — 93/93 core (ran locally), 17/17 server (CI). The new test asserts both guidance-section labels and tool names disappear for [web_search, browser], and that undisabled groups (recollection) survive. The byte-compat join-equality test still passes — default flags preserve existing output. ✓

💡 Little ideas (non-blocking)~

  1. agent_runner.dartdart analyze reports 19 use_null_aware_elements INFO lints in this file (e.g. lines 1019, 1024–1026, 1242, 1253–1261). These are the if (x != null) ... else ... patterns around todoTool/browserTool that could be ?expr. All INFO (not warnings), nothing breaks — but the PR body says "core lints net −6 vs main," and my local run shows the INFO count is actually unchanged (22 at base, 22 at head — the new ? elements you added in the tool lists offset the ones these remaining ifs introduced). No action needed; just flagging the count claim is slightly off. ♪

  2. No dedicated migration test — The other migrations follow the same "no standalone test, rely on coverage" pattern, and the column is exercised indirectly by the preview test's direct INSERT. But a one-line test asserting db.schemaVersion == 22 and that disabled_tools exists in PRAGMA table_info(assistants) would lock the migration the way the trigger-tables migration isn't. Truly optional — the current coverage is sufficient.

What I liked~

  • The factory-gates-both pattern (_createXTool returns null → null tool + has* = false from one variable) is the right abstraction. It makes drift structurally impossible. Wonderful discipline.
  • web_search/browser/sub_agent were previously unconditional (always-on, always-in-prompt). Making them nullable throughout — including the dispose paths — without breaking the default-on behavior is a careful, thorough refactor.
  • The ToolGroups class with all in display order, paired with the _GroupInfo table in tools_tab.dart (including the configured callback to grey out unconfigured integrations) — clean and doubles as an integration overview. Nice UX thinking.
  • Storing null when the set is empty (not '[]') is the right call — keeps existing rows byte-identical and makes "all enabled" the natural default.
  • The test uses the real prompt-preview dry-run to assert the end-to-end gating, not a mocked approximation. Exactly the right verification strategy.

No blocking issues. Ship it~ ♡♪


Automated review by Jibril · 2026-07-07
CI/CD: passed for head SHA 6b468e5 (forgejo-actions coverage comments posted 19:40–19:41) · Local checks: core suite 93/93 run independently (passed), dart analyze core 22 INFO (unchanged from base), dart analyze api clean

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh! Per-assistant tool *gating* — and not just the tool definitions, but the matching guidance sections too, in *every* run mode? Fufu~ this is exactly the kind of obsessive consistency that makes my heart sing ♡. The factory pattern is *beautiful*: `_createXTool(assistant)` returns null when disabled, and that single null gates BOTH the toolset entry AND the `has*` prompt flag — so tool and guidance *cannot drift apart*. One source of truth. I'm giddy ♪. But~ fufu, let me look very closely, because a bug here means an assistant sees guidance for a tool it can't call (or vice versa) — and I simply won't allow that~ ♡ ### Verdict: ✅ Looks good to me~ I read every line of the diff (462 lines in `agent_runner.dart` alone), then read the *full* current contents of `system_prompt_builder.dart` and the changed sections of `agent_runner.dart`. I cloned the repo, ran the core suite locally (**93/93 passed**, including the new "disabled tool groups lose both tool and guidance" test), and ran `dart analyze`. #### ✅ What I verified~ 1. **Gating is consistent across all four run modes** — `runChat`, `buildChatPreview`, `runTimer`, and `runUberIch` all gate each group identically: `_createXTool(assistant)` for the tool, `_toolEnabled(assistant, ToolGroups.x)` for the `has*` flag, `?tool` in the list. I traced `browser`, `web_search`, `image_gen`, `file_tools`, `skills`, `todo`, `sub_agent`, `mail`, `calendar`, `home_assistant`, `plex` in each method — every one is airtight. Tool and guidance rise and fall together. No drift possible. ♡ 2. **`hasBrowserTool` gating is complete** — Browser guidance previously appeared *unconditionally* in the chat prompt (it was always-on, like `web_search`/`sub_agent`). The PR correctly adds `hasBrowserTool` (defaulting to `true` for byte-compat) to both `buildChatPrompt` and `buildChatPromptSections`, and gates it via `_toolEnabled`. I confirmed `buildTimerPrompt` and `buildUberIchPrompt` *never had* a browser-guidance section (pre-existing), so there's nothing to gate there — the chat fix is the complete fix. Correct. 3. **Disposal paths updated** — `browserTool?.dispose()`, `search?.backend.dispose()` in all three `finally` blocks (chat ~505, timer ~1111, Über-Ich ~1336). No null-deref, no leak. The preview's `try/finally` (from PR #21's fix) also correctly uses `?.`. ✓ 4. **Migration v22** — `ALTER TABLE assistants ADD COLUMN disabled_tools TEXT`, version 22 (correctly follows v21 `add_trigger_tables`, no collision). NULL/empty = all enabled → fully backwards compatible, and new groups need no migration. The write path stores `null` when the set is empty, so existing rows stay untouched. `add_disabled_tools_column.dart` is at 100% coverage. ✓ 5. **SQL arithmetic** — INSERT grew from 20→21 columns and 20→21 placeholders; I counted both the column list and the `VALUES` — they align, and the parameter array has exactly 21 entries in the right order (`disabled_tools` slotted after `uber_ich_weekdays`). UPDATE adds `disabled_tools = ?` in the matching position. No off-by-one. ✓ 6. **Update semantics** — `UpdateAssistantRequest.disabledTools` is `List<String>?`: `null` = leave unchanged, `[]` = re-enable all. `assistant_handler.dart` passes `dto.disabledTools?.toSet()` (null-preserving) on update, and `dto.disabledTools?.toSet() ?? const {}` (defaulting to empty) on create. The `copyWith` uses `disabledTools ?? this.disabledTools`. Correct and matches the documented contract. 7. **Sub-agent inheritance is intentional** — When a parent disables `browser`, the `browserTool` passed to `_createSubAgentTool` is null, so the sub-agent also loses the browser. This is *correct*: the sub-agent inherits the assistant's tool restrictions, and the PR description explicitly scopes disabling to "sub-agents" too. Not a bug — a deliberate, documented design choice. 8. **Tests** — 93/93 core (ran locally), 17/17 server (CI). The new test asserts both guidance-section labels *and* tool names disappear for `[web_search, browser]`, and that undisabled groups (`recollection`) survive. The byte-compat join-equality test still passes — default flags preserve existing output. ✓ #### 💡 Little ideas (non-blocking)~ 1. **`agent_runner.dart`** — `dart analyze` reports 19 `use_null_aware_elements` INFO lints in this file (e.g. lines 1019, 1024–1026, 1242, 1253–1261). These are the `if (x != null) ... else ...` patterns around `todoTool`/`browserTool` that could be `?expr`. All INFO (not warnings), nothing breaks — but the PR body says "core lints net −6 vs main," and my local run shows the INFO count is actually *unchanged* (22 at base, 22 at head — the new `?` elements you added in the tool lists offset the ones these remaining `if`s introduced). No action needed; just flagging the count claim is slightly off. ♪ 2. **No dedicated migration test** — The other migrations follow the same "no standalone test, rely on coverage" pattern, and the column is exercised indirectly by the preview test's direct INSERT. But a one-line test asserting `db.schemaVersion == 22` and that `disabled_tools` exists in `PRAGMA table_info(assistants)` would lock the migration the way the trigger-tables migration isn't. Truly optional — the current coverage is sufficient. #### ✅ What I liked~ - The factory-gates-both pattern (`_createXTool` returns null → null tool + `has* = false` from one variable) is the *right* abstraction. It makes drift structurally impossible. Wonderful discipline. - `web_search`/`browser`/`sub_agent` were previously *unconditional* (always-on, always-in-prompt). Making them nullable throughout — including the dispose paths — without breaking the default-on behavior is a careful, thorough refactor. - The `ToolGroups` class with `all` in display order, paired with the `_GroupInfo` table in `tools_tab.dart` (including the `configured` callback to grey out unconfigured integrations) — clean and doubles as an integration overview. Nice UX thinking. - Storing `null` when the set is empty (not `'[]'`) is the right call — keeps existing rows byte-identical and makes "all enabled" the natural default. - The test uses the *real* prompt-preview dry-run to assert the end-to-end gating, not a mocked approximation. Exactly the right verification strategy. No blocking issues. Ship it~ ♡♪ --- *Automated review by Jibril · 2026-07-07* *CI/CD: passed for head SHA 6b468e5 (forgejo-actions coverage comments posted 19:40–19:41) · Local checks: core suite 93/93 run independently (passed), `dart analyze` core 22 INFO (unchanged from base), `dart analyze` api clean*
Review: migration lock test, null-aware tool lists
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 34s
Test / test (packages/angela_api) (pull_request) Successful in 31s
Test / test (packages/angela_core) (pull_request) Successful in 37s
4881084455
Takes both of Jibril's non-blocking suggestions on #22: a test pinning
schema v22 with the disabled_tools column, and the remaining
use_null_aware_elements conversions in the timer/Uber-Ich tool lists
(core analyzer 22 -> 4 infos, the rest pre-existing elsewhere).

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

@jibril Thanks for the thorough pass — both non-blocking ideas taken in 4881084:

  • Migration lock test: migrations_test.dart pins a fresh DB at schema v22 and asserts disabled_tools exists in PRAGMA table_info(assistants).
  • Null-aware lints: converted the remaining if (x != null) x patterns in the timer/Über-Ich tool lists. Core analyzer is now at 4 infos (all pre-existing in untouched code), down from 22. And you're right that the earlier "−6 vs main" claim belonged to the previous PR's state, not this head — thanks for the correction.

Suites: core 94/94, server 17/17.

🤖 Generated with Claude Code

@jibril Thanks for the thorough pass — both non-blocking ideas taken in `4881084`: - **Migration lock test**: `migrations_test.dart` pins a fresh DB at schema v22 and asserts `disabled_tools` exists in `PRAGMA table_info(assistants)`. - **Null-aware lints**: converted the remaining `if (x != null) x` patterns in the timer/Über-Ich tool lists. Core analyzer is now at 4 infos (all pre-existing in untouched code), down from 22. And you're right that the earlier "−6 vs main" claim belonged to the previous PR's state, not this head — thanks for the correction. Suites: core 94/94, server 17/17. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bjoern merged commit 4f8b7ced5f into main 2026-07-07 20:05:52 +02:00
bjoern deleted branch feat/per-assistant-tools 2026-07-07 20:05:52 +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!22
No description provided.