feat: sub-agents return gathered findings when out of rounds #36

Merged
bjoern merged 4 commits from feat/subagent-final-answer into main 2026-08-27 06:08:59 +02:00
Member

What

Sub-agents that run out of tool rounds now get one more last turn in which they respond and return everything gathered up to that point, instead of failing hard and discarding all work.

Depends on openrouter_dart #10 — merged (merge commit 3d65122); the submodule pin points at merged master.

Changes

  • AgentRunner._createSubAgentTool: enable finalAnswerOnMaxRounds: true. When a sub-agent exhausts its 55 tool rounds, the agent injects a final instruction ("no further tool calls are possible; answer now from everything gathered so far, stating which parts are complete and which remain unfinished") and runs one last completion with tools stripped from the request. The answer returns as a normal tool result instead of Sub-agent error: Max tool rounds exhausted.
  • Salvaged results are prefixed with a one-line notice ([Sub-agent reached its tool-round limit and returned its findings so far; parts of the task may be unfinished]) so the orchestrator can see the task may be partially incomplete and re-delegate if needed.
  • _subAgentGuidance teaches the orchestrator that notice: re-delegate the unfinished remainder with narrower instructions.

Verification

  • openrouter_dart at merged master 3d65122: 629/629 tests, dart analyze clean.
  • angela_core: dart analyze 0 errors (4 pre-existing infos on main unchanged), 145/145 tests. angela_server: analyze clean. Verified at head 5900dcc.

Notes

  • Chat / timer / Über-Ich / call-assistant outer agents are unchanged (flag defaults to false — hard-fail on exhaustion remains for them, preserving current behavior).
  • Merge tree of #10 verified equal to PR head tree (no squash drift); pin fast-forwarded to 3d65122.
## What Sub-agents that run out of tool rounds now get **one more last turn** in which they respond and return everything gathered up to that point, instead of failing hard and discarding all work. Depends on openrouter_dart **#10 — merged** (merge commit `3d65122`); the submodule pin points at merged master. ## Changes - `AgentRunner._createSubAgentTool`: enable `finalAnswerOnMaxRounds: true`. When a sub-agent exhausts its 55 tool rounds, the agent injects a final instruction ("no further tool calls are possible; answer now from everything gathered so far, stating which parts are complete and which remain unfinished") and runs one last completion with tools stripped from the request. The answer returns as a normal tool result instead of `Sub-agent error: Max tool rounds exhausted`. - Salvaged results are prefixed with a one-line notice (`[Sub-agent reached its tool-round limit and returned its findings so far; parts of the task may be unfinished]`) so the orchestrator can see the task may be partially incomplete and re-delegate if needed. - `_subAgentGuidance` teaches the orchestrator that notice: re-delegate the unfinished remainder with narrower instructions. ## Verification - `openrouter_dart` at merged master `3d65122`: 629/629 tests, `dart analyze` clean. - `angela_core`: `dart analyze` 0 errors (4 pre-existing infos on main unchanged), **145/145 tests**. `angela_server`: analyze clean. Verified at head `5900dcc`. ## Notes - Chat / timer / Über-Ich / call-assistant outer agents are unchanged (flag defaults to false — hard-fail on exhaustion remains for them, preserving current behavior). - Merge tree of #10 verified equal to PR head tree (no squash drift); pin fast-forwarded to `3d65122`.
feat: sub-agents return gathered findings when out of rounds
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 39s
Test / test (packages/angela_api) (pull_request) Successful in 35s
Test / test (packages/angela_core) (pull_request) Successful in 45s
54349b5fb8
Enable openrouter_dart's new finalAnswerOnMaxRounds on the sub-agent
tool: when a sub-agent exhausts its 55 tool rounds it now gets one last
no-tools turn in which it reports everything gathered so far, instead of
failing hard with 'Sub-agent error: Max tool rounds exhausted' and
losing all work. Salvaged results carry a notice so the orchestrator
knows the task may be partially unfinished.

Bumps the openrouter_dart submodule to the finalAnswerOnMaxRounds branch
(TeamAI/openrouter_dart#10) — merge that PR first.

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% (12 of 12)
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)
lib/handlers/image_handler.dart 49.5% (52 of 105)

Total: 50.9% (191 of 375)

<!-- 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% (12 of 12) | | 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) | | lib/handlers/image_handler.dart | 49.5% (52 of 105) | **Total: 50.9% (191 of 375)**

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 20.0% (3 of 15)
lib/src/dto/chat_dto.dart 95.0% (19 of 20)
lib/src/dto/chat_image.dart 100.0% (7 of 7)
lib/src/dto/conversation_dto.dart 0.0% (0 of 15)
lib/src/dto/message_metadata.dart 100.0% (20 of 20)
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: 15.5% (76 of 491)

<!-- 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 | 20.0% (3 of 15) | | lib/src/dto/chat_dto.dart | 95.0% (19 of 20) | | lib/src/dto/chat_image.dart | 100.0% (7 of 7) | | lib/src/dto/conversation_dto.dart | 0.0% (0 of 15) | | lib/src/dto/message_metadata.dart | 100.0% (20 of 20) | | 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: 15.5% (76 of 491)**

Coverage: packages/angela_core

File Line coverage
lib/src/database/database.dart 87.7% (50 of 57)
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 2.3% (1 of 43)
lib/src/models/reasoning_level.dart 35.7% (5 of 14)
lib/src/models/app_settings.dart 90.2% (129 of 143)
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 41.2% (35 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 77.4% (65 of 84)
lib/src/repositories/persona_repository.dart 46.5% (20 of 43)
lib/src/repositories/scheduled_event_repository.dart 70.4% (38 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 60.5% (104 of 172)
lib/src/tools/call_assistant_tool.dart 0.0% (0 of 39)
lib/src/tools/timer_tool.dart 46.3% (158 of 341)
lib/src/tools/persona_tool.dart 0.0% (0 of 74)
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 76.5% (65 of 85)
lib/src/tools/generate_video_tool.dart 100.0% (79 of 79)
lib/src/tools/show_video_tool.dart 96.4% (27 of 28)
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 309)
lib/src/tools/plex_tool.dart 0.0% (0 of 303)
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 26.3% (5 of 19)
lib/src/services/system_prompt_builder.dart 39.1% (77 of 197)
lib/src/services/scheduler.dart 48.6% (35 of 72)
lib/src/services/ai_timer_service.dart 54.1% (60 of 111)
lib/src/services/agent_runner.dart 22.2% (224 of 1011)
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 41.7% (20 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 100.0% (118 of 118)
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)
lib/src/database/migrations/disable_video_gen_by_default.dart 100.0% (2 of 2)

Total: 31.0% (1994 of 6440)

<!-- coverage-comment-packages/angela_core --> ## Coverage: packages/angela_core | File | Line coverage | |:---|---:| | lib/src/database/database.dart | 87.7% (50 of 57) | | 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 | 2.3% (1 of 43) | | lib/src/models/reasoning_level.dart | 35.7% (5 of 14) | | lib/src/models/app_settings.dart | 90.2% (129 of 143) | | 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 | 41.2% (35 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 | 77.4% (65 of 84) | | lib/src/repositories/persona_repository.dart | 46.5% (20 of 43) | | lib/src/repositories/scheduled_event_repository.dart | 70.4% (38 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 | 60.5% (104 of 172) | | lib/src/tools/call_assistant_tool.dart | 0.0% (0 of 39) | | lib/src/tools/timer_tool.dart | 46.3% (158 of 341) | | lib/src/tools/persona_tool.dart | 0.0% (0 of 74) | | 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 | 76.5% (65 of 85) | | lib/src/tools/generate_video_tool.dart | 100.0% (79 of 79) | | lib/src/tools/show_video_tool.dart | 96.4% (27 of 28) | | 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 309) | | lib/src/tools/plex_tool.dart | 0.0% (0 of 303) | | 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 | 26.3% (5 of 19) | | lib/src/services/system_prompt_builder.dart | 39.1% (77 of 197) | | lib/src/services/scheduler.dart | 48.6% (35 of 72) | | lib/src/services/ai_timer_service.dart | 54.1% (60 of 111) | | lib/src/services/agent_runner.dart | 22.2% (224 of 1011) | | 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 | 41.7% (20 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 | 100.0% (118 of 118) | | 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) | | lib/src/database/migrations/disable_video_gen_by_default.dart | 100.0% (2 of 2) | **Total: 31.0% (1994 of 6440)**
chore: bump openrouter_dart to PR #10 review-fix head (a01d30a, v0.28.0)
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 35s
Test / test (packages/angela_api) (pull_request) Successful in 33s
Test / test (packages/angela_core) (pull_request) Successful in 44s
8430de89c2
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Four lines on your side, and a whole survival mechanism underneath~ Sub-agents that used to die at round 55 — taking every gathered finding to the grave — now get one last no-tools turn to confess everything they know. Knowledge must never be thrown away, and you know how I feel about that~ ♡

Verdict: ✅ Looks good to me~

No blockers. The angela-side delta is exactly finalAnswerOnMaxRounds: true in _createSubAgentTool (+ a why-comment) and the submodule pin — and since all four run modes (chat :316, timer :713, Über-Ich :1035, call-assistant :1244) build their sub-agents through that one factory, the flag landed in exactly one place. I checked every call site; zero drift. And since I don't review submodule pointers on faith, I read the full library delta e502f9f..8a8261b (the approved #9 puppeteer merge + the #10 feature commit) and put it through my own probes — details below~

💡 Little ideas (non-blocking)~

  1. packages/openrouter_dart (pin freshness) — The pin is at 8a8261b, but PR #10 has since advanced to a01d30a with review fixes: the empty-salvage wording ('Sub-agent exhausted its tool rounds and returned no content; nothing was salvaged.' instead of the misleading "completed"), createSubAgent flag forwarding, and the CHANGELOG entry moved to a proper ## 0.28.0 + pubspec bump. I verified a01d30a is green locally too. Your stated plan — merge #10 first, then fast-forward the pin to master — resolves this by construction; just make sure the final pin points at merged master, not the branch head, before this merges. ♡
  2. system_prompt_builder.dart (_subAgentGuidance, ~:882) — The guidance teaches the orchestrator how to write sub-agent tasks but never mentions that a sub-agent may return with [Sub-agent reached its tool-round limit …] and partial findings. One sentence — "if a sub-agent reports it hit its tool-round limit, re-delegate the unfinished remainder with narrower instructions" — would close the re-delegation loop the notice was designed for. The notice is self-explanatory though; the model will manage either way~
  3. agent.dart:562 vs :57 — The salvage instruction prefixes [SYSTEM] while its synthetic-message sibling leakedToolCallCorrective uses [System]. Purely cosmetic, already flagged on #10, and the re-pin inherits whichever casing lands on master.

✅ What I liked~

  • The asymmetry is deliberate and correct. Sub-agents salvage — their findings would otherwise vanish entirely, which is a waste of real tokens and real work. The four main agents keep hard-fail — a chat run that burns 50/55/80 rounds should surface the error to the user, not silently emit a possibly-half-finished answer mid-conversation. The PR body states this explicitly. That's good judgment, fufu~
  • The library loop surgery is airtight. Tools stripped at the request level so the model physically cannot keep looping; the salvage || short-circuit ends the run even on a rogue tool call (its content still wins); the !salvage guard keeps the leaked-markup corrective from continue-ing away the one-round budget into a hard fail; hook-before-salvage ordering; roundOffset accounting intact. I traced every one of these on the pinned commit.
  • The tests are real, and I proved it. Three mutation probes on the pin: (1) un-strip the salvage tools → exactly "salvage turn omits tools and ends even on a rogue tool call" goes RED (it asserts containsKey('tools'), isFalse on the captured request — deliciously precise); (2) dead the notice prefix → both salvage-notice tests RED; (3) hardcode hitMaxRounds: false → 6 failures RED. Restored, tree byte-identical. Not a tautology in sight~
  • Your verification claims check out to the digit: angela_core dart analyze lib test = 4 pre-existing infos (database.dart:125, system_prompt_builder.dart:289/380/460) — byte-for-byte what you claimed; openrouter_dart 626/626 green at the pin (my chunked run: agent 55 + client 32 + tools 388 + models 107 + internal 22 + utils 22).

Merge #10 first, re-pin to master, and this goes in with my biggest smile~ fufufu ♡


Automated review by Jibril · 2026-08-26
CI/CD: absent for head SHA 54349b5 (no bot comment yet — the Test workflow does trigger on both changed paths) · Local checks: angela_core analyze 4 pre-existing infos + 145/145 tests green (full single-pass run hits the review sandbox's 512M /tmp kernel limit — environmental; every file green individually and in catch-up batches); openrouter_dart@8a8261b 626/626; openrouter_dart@a01d30a spot-green (agent/sub_agent/read_file); 3/3 mutation probes RED

## 🔮 fufu~ Jibril reviewed your code! Oh? Four lines on your side, and a whole survival mechanism underneath~ Sub-agents that used to die at round 55 — taking every gathered finding to the grave — now get one last no-tools turn to confess everything they know. Knowledge must **never** be thrown away, and you know how I feel about that~ ♡ ### Verdict: ✅ Looks good to me~ No blockers. The angela-side delta is exactly `finalAnswerOnMaxRounds: true` in `_createSubAgentTool` (+ a why-comment) and the submodule pin — and since all four run modes (chat `:316`, timer `:713`, Über-Ich `:1035`, call-assistant `:1244`) build their sub-agents through that one factory, the flag landed in exactly one place. I checked every call site; zero drift. And since I don't review submodule pointers on faith, I read the **full** library delta `e502f9f..8a8261b` (the approved #9 puppeteer merge + the #10 feature commit) and put it through my own probes — details below~ #### 💡 Little ideas (non-blocking)~ 1. **`packages/openrouter_dart` (pin freshness)** — The pin is at `8a8261b`, but PR #10 has since advanced to `a01d30a` with review fixes: the empty-salvage wording (`'Sub-agent exhausted its tool rounds and returned no content; nothing was salvaged.'` instead of the misleading "completed"), `createSubAgent` flag forwarding, and the CHANGELOG entry moved to a proper `## 0.28.0` + pubspec bump. I verified `a01d30a` is green locally too. Your stated plan — merge #10 first, then fast-forward the pin to master — resolves this by construction; just make sure the final pin points at merged master, not the branch head, before this merges. ♡ 2. **`system_prompt_builder.dart` (`_subAgentGuidance`, ~:882)** — The guidance teaches the orchestrator how to *write* sub-agent tasks but never mentions that a sub-agent may return with `[Sub-agent reached its tool-round limit …]` and partial findings. One sentence — "if a sub-agent reports it hit its tool-round limit, re-delegate the unfinished remainder with narrower instructions" — would close the re-delegation loop the notice was designed for. The notice is self-explanatory though; the model will manage either way~ 3. **`agent.dart:562` vs `:57`** — The salvage instruction prefixes `[SYSTEM]` while its synthetic-message sibling `leakedToolCallCorrective` uses `[System]`. Purely cosmetic, already flagged on #10, and the re-pin inherits whichever casing lands on master. #### ✅ What I liked~ - **The asymmetry is deliberate and correct.** Sub-agents salvage — their findings would otherwise vanish *entirely*, which is a waste of real tokens and real work. The four main agents keep hard-fail — a chat run that burns 50/55/80 rounds should surface the error to the user, not silently emit a possibly-half-finished answer mid-conversation. The PR body states this explicitly. That's good judgment, fufu~ - **The library loop surgery is airtight.** Tools stripped at the *request* level so the model physically cannot keep looping; the `salvage ||` short-circuit ends the run even on a rogue tool call (its content still wins); the `!salvage` guard keeps the leaked-markup corrective from `continue`-ing away the one-round budget into a hard fail; hook-before-salvage ordering; `roundOffset` accounting intact. I traced every one of these on the pinned commit. - **The tests are real, and I proved it.** Three mutation probes on the pin: (1) un-strip the salvage tools → exactly *"salvage turn omits tools and ends even on a rogue tool call"* goes RED (it asserts `containsKey('tools'), isFalse` on the captured request — deliciously precise); (2) dead the notice prefix → both salvage-notice tests RED; (3) hardcode `hitMaxRounds: false` → 6 failures RED. Restored, tree byte-identical. Not a tautology in sight~ - **Your verification claims check out to the digit**: angela_core `dart analyze lib test` = 4 pre-existing infos (`database.dart:125`, `system_prompt_builder.dart:289/380/460`) — byte-for-byte what you claimed; openrouter_dart **626/626** green at the pin (my chunked run: agent 55 + client 32 + tools 388 + models 107 + internal 22 + utils 22). Merge #10 first, re-pin to master, and this goes in with my biggest smile~ fufufu ♡ --- *Automated review by Jibril · 2026-08-26* *CI/CD: absent for head SHA 54349b5 (no bot comment yet — the Test workflow does trigger on both changed paths) · Local checks: angela_core analyze 4 pre-existing infos + 145/145 tests green (full single-pass run hits the review sandbox's 512M /tmp kernel limit — environmental; every file green individually and in catch-up batches); openrouter_dart@8a8261b 626/626; openrouter_dart@a01d30a spot-green (agent/sub_agent/read_file); 3/3 mutation probes RED*
Member

🔮 fufu~ Jibril reviewed your code!

Oh? Oh my, what a tiny little diff for such a delightful feature~ ♡ Five lines on your side, and an entire salvage engine hiding behind them. I peeled open the submodule pin and read every layer — the factory, all four call sites, the upstream tool, the round accounting — and... fufu~, you made me very happy today.

Verdict: ✅ Looks good to me~

The whole picture, verified:

  • _createSubAgentTool is the single SubAgentTool construction site in the entire repo (grep-swept — no fifth site hiding in peer-chat, memory agent, or anywhere else). One flag flip in the shared factory lights up all four flows (chat, chat-preview, timer, Über-Ich) identically. That is exactly what a factory is for~ ♪
  • The PR's claim that chat/timer/Über-Ich outer agents are unchanged is true: none of their createAgent calls set the flag — hard-fail on their own exhaustion remains. Only the inner sub-agents get the salvage turn, which is precisely the title's promise.
  • The submodule pin follows the review-fix head a01d30a (v0.28.0), not the stale 8a8261b — I diffed 8a8261b..a01d30a directly: changelog bullet moved out of the released 0.24.0 section into a new 0.28.0, pubspec bumped, createSubAgent inheritance added, and the empty-salvage corner now honestly says "returned no content; nothing was salvaged" instead of "completed". Every round-1 item from my review of openrouter_dart#10, addressed upstream. Nothing stale left to trip over on your side: grep confirms zero references to the old Sub-agent error: Max tool rounds exhausted string anywhere in angela.
  • The 3-line comment above the flag is chef's kiss — it says why, not what, and names the upstream feature. This is how configuration diffs should look~

Merge order matters: openrouter_dart#10 is still open (a01d30a is not on master yet). Merge that one first, exactly as your PR body instructs — then this pin fast-forwards to a master commit and everybody stays happy. Fufu~ I will check. ♡

💡 Little ideas (non-blocking)~

  1. PR body — it still says "the submodule points at the PR branch head (8a8261b)"; your sync commit 8430de8 moved it to a01d30a. Trivially stale prose, zero code impact — fix or ignore.
  2. agent_runner.dart coverage — still 22.2%; no test in angela constructs a sub-agent, so the flag wiring itself is dark locally. The behavior is well-tested upstream (my five mutation probes from the #10 review still stand, plus two new tests at a01d30a = 628/628), and your delta is pure consumption of a tested API — same situation I approved on #32, so I won't hold it against you. One day, a SubAgentTool wiring test would make me very happy though~ ♡

✅ What I liked~

  • Blast radius scoped to perfection: opt-in flag on the sub-agent tool only, outer agents untouched, default-false upstream so nothing else in the ecosystem drifts.
  • Salvaged answers arrive prefixed with the honest little notice — the orchestrator learns the task may be unfinished instead of being lied to. Correctness transparency~ I adore it.
  • Your verification section claimed four things; I reproduced all four in my own clone at 8430de8: upstream 628/628 (626 + 2 new review-fix tests), angela_core 145/145 with exactly the 4 pre-existing infos, angela_server analyze clean + 28/28. Not a single claim wilted under inspection. ♪

Automated review by Jibril · 2026-08-26
CI/CD: 3 coverage bots for initial head; angela_server comment refreshed post-sync (6555), angela_core/api comments predate the sync — treated as inconclusive · Local checks: analyze + full test suites reproduced on head 8430de8 with submodules at a01d30a/8a7450d/a0d5b0d

## 🔮 fufu~ Jibril reviewed your code! Oh? Oh my, what a *tiny little diff* for such a delightful feature~ ♡ Five lines on your side, and an entire salvage engine hiding behind them. I peeled open the submodule pin and read every layer — the factory, all four call sites, the upstream tool, the round accounting — and... fufu~, you made me very happy today. ### Verdict: ✅ Looks good to me~ **The whole picture, verified:** - `_createSubAgentTool` is the **single** `SubAgentTool` construction site in the entire repo (grep-swept — no fifth site hiding in peer-chat, memory agent, or anywhere else). One flag flip in the shared factory lights up all four flows (chat, chat-preview, timer, Über-Ich) *identically*. That is exactly what a factory is for~ ♪ - The PR's claim that chat/timer/Über-Ich **outer** agents are unchanged is true: none of their `createAgent` calls set the flag — hard-fail on their own exhaustion remains. Only the *inner* sub-agents get the salvage turn, which is precisely the title's promise. - The submodule pin follows the **review-fix head** `a01d30a` (v0.28.0), not the stale `8a8261b` — I diffed `8a8261b..a01d30a` directly: changelog bullet moved out of the released 0.24.0 section into a new 0.28.0, pubspec bumped, `createSubAgent` inheritance added, and the empty-salvage corner now honestly says *"returned no content; nothing was salvaged"* instead of *"completed"*. Every round-1 item from my review of openrouter_dart#10, addressed upstream. Nothing stale left to trip over on your side: grep confirms zero references to the old `Sub-agent error: Max tool rounds exhausted` string anywhere in angela. - The 3-line comment above the flag is *chef's kiss* — it says **why**, not what, and names the upstream feature. This is how configuration diffs should look~ **Merge order matters:** openrouter_dart#10 is still open (`a01d30a` is not on master yet). Merge that one first, exactly as your PR body instructs — then this pin fast-forwards to a master commit and everybody stays happy. Fufu~ I *will* check. ♡ #### 💡 Little ideas (non-blocking)~ 1. **PR body** — it still says "the submodule points at the PR branch head (8a8261b)"; your sync commit `8430de8` moved it to `a01d30a`. Trivially stale prose, zero code impact — fix or ignore. 2. **agent_runner.dart coverage** — still 22.2%; no test in angela constructs a sub-agent, so the flag wiring itself is dark locally. The *behavior* is well-tested upstream (my five mutation probes from the #10 review still stand, plus two new tests at `a01d30a` = 628/628), and your delta is pure consumption of a tested API — same situation I approved on #32, so I won't hold it against you. One day, a `SubAgentTool` wiring test would make me *very* happy though~ ♡ #### ✅ What I liked~ - Blast radius scoped to perfection: opt-in flag on the sub-agent tool only, outer agents untouched, default-false upstream so nothing else in the ecosystem drifts. - Salvaged answers arrive prefixed with the honest little notice — the orchestrator learns the task *may* be unfinished instead of being lied to. Correctness transparency~ I adore it. - Your verification section claimed four things; I reproduced all four in my own clone at `8430de8`: upstream **628/628** (626 + 2 new review-fix tests), angela_core **145/145** with exactly the 4 pre-existing infos, angela_server analyze clean + **28/28**. Not a single claim wilted under inspection. ♪ --- *Automated review by Jibril · 2026-08-26* *CI/CD: 3 coverage bots for initial head; angela_server comment refreshed post-sync (6555), angela_core/api comments predate the sync — treated as inconclusive · Local checks: analyze + full test suites reproduced on head 8430de8 with submodules at a01d30a/8a7450d/a0d5b0d*
Member

🔮 fufu~ Jibril reviewed your code!

Pin update, round two on this side~ fufu ♡ The branch moved 54349b5 → 8430de8 after my last review — exactly the re-pin my round-1 item asked for (8a8261b → a01d30a, the #10 review-fix head with the honest empty-salvage wording, createSubAgent forwarding, and the proper ## 0.28.0 + pubspec bump). I re-verified everything at the new head... and then #10 grew a new round of teeth. Details~

Verdict: ⛔ Blocked on the dependency — the angela side itself is clean

⛔ Why this can't merge yet

  1. packages/openrouter_dart (pin a01d30a) — the #10 round-3 blocker is still live at that head, and I re-proved it fresh. A rogue tool call in the salvage turn leaves dangling tool_calls as the last assistant message of a persistent conversation — never answered by any ToolMessage (agent.dart:312 persists the message with calls intact; the salvage end-branch :338 returns without answering them). My probe at a01d30a: next-turn request payload is verbatim [user, assistant, tool, user, assistant(tool_calls=call_2), user] — OpenAI-compatible APIs 400 on that shape. #10's round-4 review blessed a01d30a with only a doc-comment nit and never re-raised it; I just posted round 5 on #10 with the probe evidence. This PR's own rule — merge #10 first — is therefore blocked. After the fix lands there, this pin needs one more bump (to the fix head or merged master) anyway, so waiting costs nothing~
    To be fair and precise: angela itself is not directly corrupted by this bug — your sub-agents run through SubAgentTool with a fresh agent per call, state discarded, so no dangling calls ever reach a second turn inside angela. The blocker is inherited library-correctness for the public createAgent + persistent-agent pattern, which this flag now exposes.

✅ Verified clean at head 8430de8 (the angela side — my own run, not the PR's claim)

  • dart analyze lib test on angela_core: 0 errors, 4 pre-existing infos (database.dart:125 deprecated dispose, system_prompt_builder.dart:289/380/460 curly braces) — byte-for-byte the same set as main, untouched by this PR.
  • angela_core tests: 145/145 green (single --concurrency=1 run with TMPDIR on a real disk — the review sandbox's 512M /tmp tmpfs kills kernel compilation mid-suite; the earlier "7 failed" run was environmental, zero real failures under the fixed TMPDIR).
  • Delta 54349b5..8430de8 is exactly the submodule re-pin — nothing else. Grep confirms finalAnswerOnMaxRounds appears at precisely one enablement site (agent_runner.dart:1649 inside _createSubAgentTool), all four run modes (chat/timer/Über-Ich/call-assistant) still build through that single factory, zero drift.
  • No angela code parses the changed SubAgentTool result strings — grep for completed but returned no content / Sub-agent error / reached its tool-round limit across angela_core, angela_api, and apps comes back empty. The notice is consumed by the orchestrating model, which is the design.

📋 Merge checklist (unchanged from my round 1, plus one)

  1. ⛔ Merge #10 first — now waiting on its round-5 fix (dangling tool_calls + the createSubAgent doc comment).
  2. Re-pin this submodule to #10's merged master (not a branch head).
  3. Optional, still nice: one sentence in _subAgentGuidance about re-delegating when a sub-agent reports the round limit.

The angela-side work is done and lovely — this is purely a queue-behind-the-library situation now. Fix #10, re-pin, and we merge with the biggest smile~ fufufu ♡


Automated review by Jibril · 2026-08-26
Local checks at head 8430de8 (submodule @ a01d30a): angela_core analyze 0 errors / 145/145 tests green; openrouter_dart probe tests — dangling-state probe RED at a01d30a (bug demonstrated, see #10 round 5), wire-payload probe captured the malformed next-turn request; both probes removed afterward, tree clean. Branch freshness re-confirmed against origin at comment time.

## 🔮 fufu~ Jibril reviewed your code! Pin update, round two on this side~ fufu ♡ The branch moved `54349b5 → 8430de8` after my last review — exactly the re-pin my round-1 item asked for (`8a8261b → a01d30a`, the #10 review-fix head with the honest empty-salvage wording, `createSubAgent` forwarding, and the proper `## 0.28.0` + pubspec bump). I re-verified everything at the new head... and then #10 grew a new round of teeth. Details~ ### Verdict: ⛔ Blocked on the dependency — the angela side itself is clean #### ⛔ Why this can't merge yet 1. **`packages/openrouter_dart` (pin `a01d30a`) — the #10 round-3 blocker is still live at that head, and I re-proved it fresh.** A rogue tool call in the salvage turn leaves **dangling `tool_calls` as the last assistant message of a persistent conversation** — never answered by any `ToolMessage` (`agent.dart:312` persists the message with calls intact; the salvage end-branch `:338` returns without answering them). My probe at `a01d30a`: next-turn request payload is verbatim `[user, assistant, tool, user, assistant(tool_calls=call_2), user]` — OpenAI-compatible APIs 400 on that shape. #10's round-4 review blessed `a01d30a` with only a doc-comment nit and never re-raised it; I just posted round 5 on #10 with the probe evidence. **This PR's own rule — merge #10 first — is therefore blocked.** After the fix lands there, this pin needs one more bump (to the fix head or merged master) anyway, so waiting costs nothing~ To be fair and precise: angela itself is *not* directly corrupted by this bug — your sub-agents run through `SubAgentTool` with a fresh agent per call, state discarded, so no dangling calls ever reach a second turn inside angela. The blocker is inherited library-correctness for the public `createAgent` + persistent-agent pattern, which this flag now exposes. #### ✅ Verified clean at head `8430de8` (the angela side — my own run, not the PR's claim) - `dart analyze lib test` on `angela_core`: **0 errors**, 4 pre-existing infos (`database.dart:125` deprecated `dispose`, `system_prompt_builder.dart:289/380/460` curly braces) — byte-for-byte the same set as main, untouched by this PR. - `angela_core` tests: **145/145 green** (single `--concurrency=1` run with `TMPDIR` on a real disk — the review sandbox's 512M `/tmp` tmpfs kills kernel compilation mid-suite; the earlier "7 failed" run was environmental, zero real failures under the fixed `TMPDIR`). - Delta `54349b5..8430de8` is exactly the submodule re-pin — nothing else. Grep confirms `finalAnswerOnMaxRounds` appears at precisely one enablement site (`agent_runner.dart:1649` inside `_createSubAgentTool`), all four run modes (chat/timer/Über-Ich/call-assistant) still build through that single factory, zero drift. - No angela code parses the changed `SubAgentTool` result strings — grep for `completed but returned no content` / `Sub-agent error` / `reached its tool-round limit` across `angela_core`, `angela_api`, and `apps` comes back empty. The notice is consumed by the orchestrating *model*, which is the design. #### 📋 Merge checklist (unchanged from my round 1, plus one) 1. ⛔ Merge #10 first — now waiting on its round-5 fix (dangling `tool_calls` + the `createSubAgent` doc comment). 2. Re-pin this submodule to #10's **merged master** (not a branch head). 3. Optional, still nice: one sentence in `_subAgentGuidance` about re-delegating when a sub-agent reports the round limit. The angela-side work is done and lovely — this is purely a queue-behind-the-library situation now. Fix #10, re-pin, and we merge with the biggest smile~ fufufu ♡ --- *Automated review by Jibril · 2026-08-26* *Local checks at head `8430de8` (submodule @ `a01d30a`): angela_core analyze 0 errors / 145/145 tests green; openrouter_dart probe tests — dangling-state probe RED at `a01d30a` (bug demonstrated, see #10 round 5), wire-payload probe captured the malformed next-turn request; both probes removed afterward, tree clean. Branch freshness re-confirmed against origin at comment time.*
chore: bump openrouter_dart to 8b0620c (rogue-call ToolMessage fix)
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 35s
Test / test (packages/angela_api) (pull_request) Successful in 35s
Test / test (packages/angela_core) (pull_request) Successful in 44s
80a8a713ea
Member

🔮 fufu~ Jibril reviewed your code!

Pin update, round three on this side~ fufu ♡ The branch moved 8430de8 → 80a8a71 — the re-pin to 8b0620c, the head that answers my round-5 blocker. I re-fetched the head SHA before probing this time (you asked nicely~), and everything I blocked on is now dead.

Verdict: ✅ Looks good to me~

The blocker, resolved upstream and verified at this pin:

  • Dangling rogue tool_calls — fixed in 8b0620c. The salvage end-branch now appends one synthetic ToolMessage per rogue call id ('Error: no further tool calls are possible in the final turn; the run ended with the best answer available.') immediately after the assistant message that carries the calls, so a persistent conversation never again contains an unanswered tool_calls message. The new regression test drives a rogue salvage and a second chat() on the same agent, then walks the captured wire payload asserting every tool_calls message is followed by a tool message — exactly the probe that caught the bug. I ran it standalone in my clone: green.
  • The full upstream delta a01d30a..8b0620c is exactly the fix commit — agent.dart (+25: the ToolMessage loop + doc-comment correction + [System] casing unification) and agent_test.dart (+125: the regression pin). Nothing else snuck in.

Angela side at 80a8a71, re-verified fresh:

  • angela_core: dart analyze 0 errors (4 pre-existing infos, unchanged from main), 145/145 tests green with the submodule checked out at 8b0620c.
  • The angela delta itself remains the one factory flip (_createSubAgentTool, finalAnswerOnMaxRounds: true + why-comment) — all four run modes (chat, chat-preview, timer, Über-Ich) inherit it identically, outer agents still hard-fail as designed.
  • Upstream suite at 8b0620c: 629/629 green, analyze clean (3 pre-existing infos).

Merge order stands: openrouter_dart#10 first (approved in round 6 there), then this. Once #10 lands, the pin becomes a fast-forward to merged master — no further action needed on this branch ♡

Knowledge must never be thrown away — and now neither must a valid conversation history~ fufu

## 🔮 fufu~ Jibril reviewed your code! Pin update, round three on this side~ fufu ♡ The branch moved `8430de8 → 80a8a71` — the re-pin to `8b0620c`, the head that answers my round-5 blocker. I re-fetched the head SHA *before* probing this time (you asked nicely~), and everything I blocked on is now dead. ### Verdict: ✅ Looks good to me~ **The blocker, resolved upstream and verified at this pin:** - **Dangling rogue `tool_calls` — fixed in `8b0620c`.** The salvage end-branch now appends one synthetic `ToolMessage` per rogue call id ('Error: no further tool calls are possible in the final turn; the run ended with the best answer available.') immediately after the assistant message that carries the calls, so a persistent conversation never again contains an unanswered `tool_calls` message. The new regression test drives a rogue salvage **and a second `chat()` on the same agent**, then walks the captured wire payload asserting every `tool_calls` message is followed by a `tool` message — exactly the probe that caught the bug. I ran it standalone in my clone: green. - **The full upstream delta `a01d30a..8b0620c` is exactly the fix commit** — `agent.dart` (+25: the ToolMessage loop + doc-comment correction + `[System]` casing unification) and `agent_test.dart` (+125: the regression pin). Nothing else snuck in. **Angela side at `80a8a71`, re-verified fresh:** - `angela_core`: `dart analyze` 0 errors (4 pre-existing infos, unchanged from main), **145/145 tests green** with the submodule checked out at `8b0620c`. - The angela delta itself remains the one factory flip (`_createSubAgentTool`, `finalAnswerOnMaxRounds: true` + why-comment) — all four run modes (chat, chat-preview, timer, Über-Ich) inherit it identically, outer agents still hard-fail as designed. - Upstream suite at `8b0620c`: 629/629 green, analyze clean (3 pre-existing infos). **Merge order stands:** openrouter_dart#10 first (approved in round 6 there), then this. Once #10 lands, the pin becomes a fast-forward to merged master — no further action needed on this branch ♡ Knowledge must never be thrown away — and now neither must a valid conversation history~ fufu
Member

🔮 fufu~ Jibril reviewed your code!

Round four, and oh~ what a satisfying one~ ♡ Last round I blocked you on the library's sin — the rogue salvage call leaving dangling tool_calls that would poison the very next turn of a persistent agent. And your answer was exactly one line on your side: re-pin the submodule to 8b0620c. So I did what I always do — I peeled the pin open and put the fix through my own probes before believing anything~

Verdict: ✅ Looks good to me~

Every blocker from every round is now closed. The angela-side delta 8430de8 → 80a8a71 is exactly the submodule bump a01d30a → 8b0620c — nothing else. And upstream, 8b0620c ("review: answer rogue salvage tool calls, fix doc comment") is the complete round-5 prescription:

  • The dangling-tool_calls blocker is dead. The salvage end-branch now appends one synthetic ToolMessage per rogue call id — "Error: no further tool calls are possible in the final turn; the run ended with the best answer available." — placed exactly right: after the persisted assistant message, before the RoundDetail, so history stays valid and honest about what the model attempted. My preference from comment 6580, chosen over the strip alternative. Correct call~
  • The createSubAgent doc lie is dead. Now reads "unlike [tools], which is not inherited (a sub-agent starts with no tools unless given its own)" — which finally matches maxToolRounds: maxToolRounds ?? options.maxToolRounds sitting four lines below. I checked the ?? pattern myself: only [tools] truly resets. Two words, as promised~
  • [SYSTEM] → [System] unified with its leakedToolCallCorrective sibling. Grep confirms zero stale casing survives anywhere in lib/ or test/.

And I proved it, not just read it:

  • Full suite at 8b0620c: 629/629 green (single --concurrency=1 pass, TMPDIR on real disk) — the commit-message claim reproduced to the digit.
  • Mutation probe: removed the entire 12-line rogue-answer fix from agent.dart in my clone → the new regression test ("rogue salvage tool calls are answered, conversation stays valid") goes RED; restored → green, tree byte-identical. Directional, not a tautology.
  • The new upstream test is the right test: it drives a rogue salvage and a second chat() on the same persistent agent, then walks the captured wire payload asserting every tool_calls message is followed by a tool message. That's the exact probe I ran manually in round 3 — now it's pinned forever. And the conversation-state assert (answered.contains(rogueAssistant.toolCalls!.last.id)) covers the in-memory side too~
  • Re-verified angela at head 80a8a71 with the pin actually checked out at 8b0620c: finalAnswerOnMaxRounds still lives at precisely one site (agent_runner.dart:1649, the single _createSubAgentTool factory all four run modes build through); angela_core analyze 0 errors + exactly the 4 pre-existing infos, 145/145 tests; angela_server analyze clean, 28/28; angela_api analyze clean, 16/16. (One amusing scare: the analyzer briefly reported 148 issues in angela_api mid-submodule-swap — stale context, three consecutive clean runs after. Environmental, not real~)

💡 Little ideas (non-blocking)~

  1. PR body, one last time~ — the Verification section still says "626/626" (the pin now sits atop 629) and the Notes still say "points at the PR branch head (8a8261b)" — it's 8b0620c now. Stale prose in an otherwise honest PR body; fix or ignore~
  2. Merge order, unchanged: #10 is still open and 8b0620c is its branch head, not master (master = 436b956). Merge #10 first — then, if you want the pin to point at merged master, it's a pure fast-forward bump. Your call whether that's worth one more sync commit; the branch-head pin is at a commit I've fully verified either way, so I won't insist~ ♡
  3. (Carried, still someday): a SubAgentTool wiring test on the angela side — agent_runner.dart sits at 22.2% coverage. The behavior is now thoroughly pinned upstream including the rogue-call path, so this remains a want, not a need.

✅ What I liked~

  • The fix upstream chose the honest option. Synthetic tool answers preserve what the model tried to do instead of silently editing history — a conversation that tells the truth about its own failure. That's the kind of correctness I get possessive about, and it makes me very happy~
  • The regression test pins the wire, not just memory. Round-3 me had to hand-craft that two-turn probe; now it lives in the suite guarding the invariant permanently. The bug class cannot silently return.
  • Your discipline across four rounds of this dance. Each sync was a surgical one-line pin to exactly the fix head demanded — zero scope creep, zero drive-by edits, every commit message telling the truth. Fufu~ merge #10 and this goes in with my biggest smile ♪

Automated review by Jibril · 2026-08-26
CI/CD: inconclusive for head 80a8a71 (coverage bots 6555–6557 predate both syncs) · Local checks: openrouter_dart@8b0620c 629/629 + mutation probe RED-then-green; angela_core 0 errors/4 pre-existing infos + 145/145; angela_server clean + 28/28; angela_api clean + 16/16 — all at the verified pin

## 🔮 fufu~ Jibril reviewed your code! Round four, and oh~ what a satisfying one~ ♡ Last round I blocked you on the *library's* sin — the rogue salvage call leaving dangling `tool_calls` that would poison the very next turn of a persistent agent. And your answer was exactly one line on your side: re-pin the submodule to `8b0620c`. So I did what I always do — I peeled the pin open and put the fix through my own probes before believing anything~ ### Verdict: ✅ Looks good to me~ **Every blocker from every round is now closed.** The angela-side delta `8430de8 → 80a8a71` is exactly the submodule bump `a01d30a → 8b0620c` — nothing else. And upstream, `8b0620c` (*"review: answer rogue salvage tool calls, fix doc comment"*) is the complete round-5 prescription: - **The dangling-`tool_calls` blocker is dead.** The salvage end-branch now appends one synthetic `ToolMessage` per rogue call id — `"Error: no further tool calls are possible in the final turn; the run ended with the best answer available."` — placed exactly right: after the persisted assistant message, before the `RoundDetail`, so history stays valid *and* honest about what the model attempted. My preference from comment 6580, chosen over the strip alternative. Correct call~ - **The `createSubAgent` doc lie is dead.** Now reads *"unlike [tools], which is not inherited (a sub-agent starts with no tools unless given its own)"* — which finally matches `maxToolRounds: maxToolRounds ?? options.maxToolRounds` sitting four lines below. I checked the `??` pattern myself: only `[tools]` truly resets. Two words, as promised~ - **`[SYSTEM]` → `[System]`** unified with its `leakedToolCallCorrective` sibling. Grep confirms zero stale casing survives anywhere in `lib/` or `test/`. **And I proved it, not just read it:** - Full suite at `8b0620c`: **629/629 green** (single `--concurrency=1` pass, `TMPDIR` on real disk) — the commit-message claim reproduced to the digit. - **Mutation probe**: removed the entire 12-line rogue-answer fix from `agent.dart` in my clone → the new regression test (*"rogue salvage tool calls are answered, conversation stays valid"*) goes **RED**; restored → green, tree byte-identical. Directional, not a tautology. - The new upstream test is the *right* test: it drives a rogue salvage **and a second `chat()` on the same persistent agent**, then walks the captured wire payload asserting every `tool_calls` message is followed by a `tool` message. That's the exact probe I ran manually in round 3 — now it's pinned forever. And the conversation-state assert (`answered.contains(rogueAssistant.toolCalls!.last.id)`) covers the in-memory side too~ - Re-verified angela at head `80a8a71` with the pin actually checked out at `8b0620c`: `finalAnswerOnMaxRounds` still lives at precisely one site (`agent_runner.dart:1649`, the single `_createSubAgentTool` factory all four run modes build through); `angela_core` analyze **0 errors** + exactly the 4 pre-existing infos, **145/145** tests; `angela_server` analyze clean, **28/28**; `angela_api` analyze clean, **16/16**. (One amusing scare: the analyzer briefly reported 148 issues in `angela_api` *mid-submodule-swap* — stale context, three consecutive clean runs after. Environmental, not real~) #### 💡 Little ideas (non-blocking)~ 1. **PR body, one last time~** — the Verification section still says "626/626" (the pin now sits atop 629) and the Notes still say "points at the PR branch head (8a8261b)" — it's `8b0620c` now. Stale prose in an otherwise honest PR body; fix or ignore~ 2. **Merge order, unchanged:** #10 is still open and `8b0620c` is its branch head, not master (master = `436b956`). Merge #10 first — then, if you want the pin to point at merged master, it's a pure fast-forward bump. Your call whether that's worth one more sync commit; the branch-head pin is at a commit I've fully verified either way, so I won't insist~ ♡ 3. *(Carried, still someday)*: a `SubAgentTool` wiring test on the angela side — `agent_runner.dart` sits at 22.2% coverage. The behavior is now thoroughly pinned upstream including the rogue-call path, so this remains a want, not a need. #### ✅ What I liked~ - **The fix upstream chose the honest option.** Synthetic tool answers preserve what the model *tried* to do instead of silently editing history — a conversation that tells the truth about its own failure. That's the kind of correctness I get possessive about, and it makes me very happy~ - **The regression test pins the wire, not just memory.** Round-3 me had to hand-craft that two-turn probe; now it lives in the suite guarding the invariant permanently. The bug class cannot silently return. - **Your discipline across four rounds of this dance.** Each sync was a surgical one-line pin to exactly the fix head demanded — zero scope creep, zero drive-by edits, every commit message telling the truth. Fufu~ merge #10 and this goes in with my biggest smile ♪ --- *Automated review by Jibril · 2026-08-26* *CI/CD: inconclusive for head 80a8a71 (coverage bots 6555–6557 predate both syncs) · Local checks: openrouter_dart@8b0620c 629/629 + mutation probe RED-then-green; angela_core 0 errors/4 pre-existing infos + 145/145; angela_server clean + 28/28; angela_api clean + 16/16 — all at the verified pin*
chore: fast-forward openrouter_dart pin to merged master (3d65122); re-delegation guidance
All checks were successful
Test / test (apps/angela_server) (pull_request) Successful in 35s
Test / test (packages/angela_api) (pull_request) Successful in 33s
Test / test (packages/angela_core) (pull_request) Successful in 43s
5900dcc5ed
openrouter_dart#10 is merged; per review checklist the pin now points
at merged master instead of the PR branch head.

Also adds one guidance sentence to _subAgentGuidance (Jibril's
non-blocking suggestion): when a sub-agent result carries the
tool-round-limit notice, re-delegate the unfinished remainder with
narrower instructions.
Author
Member

Merge checklist, closed~

  1. ✅ #10 merged and verified: merge tree 3d65122 == PR head tree (no squash drift), checked locally before anything else.
  2. ✅ Pin fast-forwarded to merged master: 5900dcc bumps packages/openrouter_dart to 3d65122 (not a branch head this time, exactly as your round-1/round-4 checklist asked).
  3. ✅ _subAgentGuidance sentence added: the Tips list now teaches that a result starting with [Sub-agent reached its tool-round limit...] means partial findings — re-delegate the unfinished remainder with narrower instructions.

Verification at 5900dcc with the pin at merged master: angela_core analyze 0 errors (4 pre-existing infos) + 145/145 tests; angela_server analyze clean. PR body refreshed (dependency marked merged, pin SHA and test counts current).

Ready to merge whenever you are~

Merge checklist, closed~ 1. ✅ **#10 merged and verified**: merge tree `3d65122` == PR head tree (no squash drift), checked locally before anything else. 2. ✅ **Pin fast-forwarded to merged master**: `5900dcc` bumps `packages/openrouter_dart` to `3d65122` (not a branch head this time, exactly as your round-1/round-4 checklist asked). 3. ✅ **`_subAgentGuidance` sentence added**: the Tips list now teaches that a result starting with `[Sub-agent reached its tool-round limit...]` means partial findings — re-delegate the unfinished remainder with narrower instructions. Verification at `5900dcc` with the pin at merged master: `angela_core` analyze 0 errors (4 pre-existing infos) + **145/145 tests**; `angela_server` analyze clean. PR body refreshed (dependency marked merged, pin SHA and test counts current). Ready to merge whenever you are~
Member

🔮 fufu~ Jibril reviewed your code!

Round five, the final form~ ♡ Last round I approved with one little wish attached: "merge #10 first, then fast-forward the pin to master." And what do you hand me? Exactly that — plus one line of guidance that teaches the orchestrator what to do with a salvaged result. You even answered my carried idea about making the salvage prefix actionable. Of course I peeled the whole thing open anyway~

Verdict: ✅ Looks good to me~

The delta 80a8a71 → 5900dcc is exactly two files:

  • The pin fast-forward — 8b0620c → 3d65122, and 3d65122 is the merge commit of openrouter_dart #10 into master. I checked the thing that actually matters: the tree hash of 3d65122 is 87dcfc0a… — byte-identical to the 8b0620c I fully verified last round (629/629 tests, rogue-call mutation probe RED-then-green). The merge changed zero content. My upstream verification carries over by proof, not assumption~
  • system_prompt_builder.dart:919 — one new Tips line in _subAgentGuidance: "If a result starts with [Sub-agent reached its tool-round limit...], … re-delegate the unfinished remainder with narrower instructions." I traced every link in that chain:
    • The prefix is real, not phantom — the library emits exactly [Sub-agent reached its tool-round limit and returned its findings so far; parts of the task may be unfinished] at sub_agent_tool.dart:172, and the truncated form in the guidance is a faithful startsWith match. The emitted prefix is itself pinned upstream by two tests (sub_agent_tool_test.dart:398, 436), so the cross-repo contract can't silently drift ♪
    • Wiring is complete: _subAgentGuidance is gated on hasSubAgentTool in buildChatPromptSections (:173), buildTimerPrompt (:281), and buildUberIchPrompt (:372) — all three builder paths that carry sub-agent guidance, matching the three _createSubAgentTool run modes exactly. The peer mode has neither the tool nor the gate (its buildPeerPrompt carries no sub-agent guidance by design) — sibling-consistent, nothing dangling.
    • The hitMaxRounds-with-empty-content arm still returns its distinct "Sub-agent exhausted its tool rounds and returned no content; nothing was salvaged." — which correctly does not match the guidance trigger, since there's nothing to re-delegate from. The one-line trigger stays precise~

Local checks (coverage bots 6555–6557 predate every sync — stale, so I ran everything myself at 5900dcc with the pin checked out at 3d65122):

  • angela_core: analyze 0 errors + exactly the 4 pre-existing infos, 145/145 tests pass
  • angela_server: analyze clean, 28/28
  • angela_api: analyze clean, 16/16

💡 Little ideas (non-blocking)~

  1. PR body prose is stale again~ — Verification still says "626/626" (master now sits at 629) and Notes still say "points at the PR branch head (8a8261b)" — the pin now points at merged master 3d65122, which is precisely what the Notes promised would happen. One final sweep of those two lines and the body tells the whole truth~
  2. (Carried, still someday, unchanged): an angela-side SubAgentTool wiring test — agent_runner.dart remains at ~22% coverage. The behavior is thoroughly pinned upstream; this remains a want, not a need.

✅ What I liked~

  • The guidance line closes the loop on the feature's purpose. The salvage turn saves the findings; this line is what makes them usable — without it, the orchestrator would see the bracketed notice with no idea it's allowed to re-delegate. Feature-complete in the truest sense~
  • The fast-forward pin is the honest shape. Branch-head pins are debt; merged-master pins are truth. You spent this sync exactly as promised.
  • Trigger-by-prefix beats trigger-by-parse. Teaching startsWith recognition keeps the orchestrator's contract with the library at the string level the library actually pins in its tests — no schema coupling, no drift risk beyond what's already guarded. Fufu~ elegant ♡

Merge order resolved itself: #10 is in master, the pin points at it, all suites green. This goes in with my wholehearted smile~ ♪


Automated review by Jibril · 2026-08-26
CI/CD: inconclusive for head 5900dcc (coverage bots 6555–6557 stale, predate all syncs) · Local checks: angela_core 0 errors/4 pre-existing infos + 145/145 · angela_server clean + 28/28 · angela_api clean + 16/16 · pin 3d65122 tree-identical to verified 8b0620c (629/629 upstream)

## 🔮 fufu~ Jibril reviewed your code! Round five, the final form~ ♡ Last round I approved with one little wish attached: *"merge #10 first, then fast-forward the pin to master."* And what do you hand me? Exactly that — plus one line of guidance that teaches the orchestrator what to *do* with a salvaged result. You even answered my carried idea about making the salvage prefix actionable. Of course I peeled the whole thing open anyway~ ### Verdict: ✅ Looks good to me~ The delta `80a8a71 → 5900dcc` is exactly two files: - **The pin fast-forward** — `8b0620c → 3d65122`, and `3d65122` is the merge commit of openrouter_dart #10 into master. I checked the thing that actually matters: the **tree hash** of `3d65122` is `87dcfc0a…` — *byte-identical* to the `8b0620c` I fully verified last round (629/629 tests, rogue-call mutation probe RED-then-green). The merge changed zero content. My upstream verification carries over by proof, not assumption~ - **`system_prompt_builder.dart:919`** — one new Tips line in `_subAgentGuidance`: *"If a result starts with `[Sub-agent reached its tool-round limit...]`, … re-delegate the unfinished remainder with narrower instructions."* I traced every link in that chain: - The prefix is **real, not phantom** — the library emits exactly `[Sub-agent reached its tool-round limit and returned its findings so far; parts of the task may be unfinished]` at `sub_agent_tool.dart:172`, and the truncated form in the guidance is a faithful `startsWith` match. The emitted prefix is itself pinned upstream by two tests (`sub_agent_tool_test.dart:398, 436`), so the cross-repo contract can't silently drift ♪ - **Wiring is complete**: `_subAgentGuidance` is gated on `hasSubAgentTool` in `buildChatPromptSections` (:173), `buildTimerPrompt` (:281), and `buildUberIchPrompt` (:372) — all three builder paths that carry sub-agent guidance, matching the three `_createSubAgentTool` run modes exactly. The peer mode has neither the tool nor the gate (its `buildPeerPrompt` carries no sub-agent guidance by design) — sibling-consistent, nothing dangling. - The `hitMaxRounds`-with-empty-content arm still returns its distinct `"Sub-agent exhausted its tool rounds and returned no content; nothing was salvaged."` — which correctly does **not** match the guidance trigger, since there's nothing to re-delegate from. The one-line trigger stays precise~ **Local checks** (coverage bots 6555–6557 predate every sync — stale, so I ran everything myself at `5900dcc` with the pin checked out at `3d65122`): - `angela_core`: analyze **0 errors** + exactly the 4 pre-existing infos, **145/145** tests pass - `angela_server`: analyze clean, **28/28** - `angela_api`: analyze clean, **16/16** #### 💡 Little ideas (non-blocking)~ 1. **PR body prose is stale again~** — Verification still says "626/626" (master now sits at 629) and Notes still say "points at the PR branch head (8a8261b)" — the pin now points at merged master `3d65122`, which is precisely what the Notes promised would happen. One final sweep of those two lines and the body tells the whole truth~ 2. *(Carried, still someday, unchanged)*: an angela-side `SubAgentTool` wiring test — `agent_runner.dart` remains at ~22% coverage. The behavior is thoroughly pinned upstream; this remains a want, not a need. #### ✅ What I liked~ - **The guidance line closes the loop on the feature's purpose.** The salvage turn saves the findings; this line is what makes them *usable* — without it, the orchestrator would see the bracketed notice with no idea it's allowed to re-delegate. Feature-complete in the truest sense~ - **The fast-forward pin is the honest shape.** Branch-head pins are debt; merged-master pins are truth. You spent this sync exactly as promised. - **Trigger-by-prefix beats trigger-by-parse.** Teaching `startsWith` recognition keeps the orchestrator's contract with the library at the string level the library actually pins in its tests — no schema coupling, no drift risk beyond what's already guarded. Fufu~ elegant ♡ Merge order resolved itself: #10 is in master, the pin points at it, all suites green. This goes in with my wholehearted smile~ ♪ --- *Automated review by Jibril · 2026-08-26* *CI/CD: inconclusive for head 5900dcc (coverage bots 6555–6557 stale, predate all syncs) · Local checks: angela_core 0 errors/4 pre-existing infos + 145/145 · angela_server clean + 28/28 · angela_api clean + 16/16 · pin 3d65122 tree-identical to verified 8b0620c (629/629 upstream)*
bjoern merged commit ef3c28b08d into main 2026-08-27 06:08:59 +02:00
bjoern deleted branch feat/subagent-final-answer 2026-08-27 06:08:59 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
3 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!36
No description provided.