feat(coach): make the AI coach agentic and the app's home-screen core - #96
Conversation
Adds the pure-Dart building blocks for the coach agent to reason over
runner data on demand instead of relying only on a fixed context dump:
- CoachToolCall/CoachToolResult/CoachToolStep entities
- CoachAgentToolCatalog: 7 tools (recent runs, run detail/splits,
health trend, training load, character progress, personal bests,
pace-plan math) with a compact system-prompt block
- CoachToolCallParser: ReAct-style `TOOL_CALL: {...}` directive parsing
- ExecuteCoachToolUseCase (+ health/context query helpers): executes
tool calls gated by the same CoachDataSource privacy permissions used
for the static coach context
Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
CoachChat._streamAgenticAnswer wraps each assistant turn: it detects a TOOL_CALL directive (peeking the buffered response so plain answers still stream live token-by-token), executes it via ExecuteCoachToolUseCase, feeds the result back as a follow-up prompt, and repeats up to 2 tool calls before forcing a final answer. Tool syntax is always stripped so it never leaks to the athlete, even if the model keeps trying past the budget. - ChatMessage gains a toolSteps field (+ codec persistence) so the UI can show which tools were used - Both the on-device (full tier only, to respect the tiny token budget) and cloud system prompts now advertise the tool catalog - executeCoachToolUseCaseProvider wires HealthRepository into the use case via shared/providers/ Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
AgentToolStepList renders a small running/success/error chip per tool call above the assistant's answer, so athletes can see what data the coach looked up instead of a hidden black box. Wired through AssistantBubble and MessageBubble. Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
Adds CoachAgentHeroCard at the top of the dashboard: a live insight
line plus one-tap prompts ('How's my training load?', 'Plan my pace
for a 10K', 'Any personal bests lately?') that showcase the coach's
new tool-calling capabilities and open Coach chat with the question
pre-filled. Makes the coach the app's home-screen entry point rather
than a hidden modal, per the KYNOS product goal of an AI-first
running coach.
Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (15)
🚧 Files skipped from review as they are similar to previous changes (12)
📝 WalkthroughWalkthroughAgentic coach tooling now spans domain models, tool parsing and execution, AI streaming, conversation persistence, coach-chat UI, dashboard entry points, tests, and regenerated project documentation. ChangesAgentic coach tools
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested labels: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Warning Tools execution failed with the following error: Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error) Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
build_runner embeds a content hash in coach_chat_provider.g.dart that was stale after editing the notifier for the agentic tool loop. Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (2)
CODEMAP.md (1)
271-271: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueMD037 false positive from underscores in class names in auto-generated table.
The markdownlint warning at line 271 is triggered by
_RunRouteScaffoldand_RouteContentin the third column — markdownlint interprets the underscores as emphasis markers with a space inside. This is a false positive since these are Dart private class names. The fix belongs in the codemap generator (tool/generate_codemap.dart): wrap the symbols column in backticks to suppress the warning.Generator fix — wrap symbols column in backticks
In
tool/generate_codemap.dart, when emitting the third column of the layer-map table, wrap the exported-symbols list in backticks:- | `$path` | $lineCount | $symbols | + | `$path` | $lineCount | `$symbols` |This prevents markdownlint from interpreting underscores in class names as emphasis markers.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CODEMAP.md` at line 271, Update the codemap generator’s layer-map table emission in generate_codemap.dart to wrap the exported-symbols column value in Markdown backticks, preserving the listed Dart symbol names while preventing underscores from being parsed as emphasis markers.Source: Linters/SAST tools
lib/features/coach_chat/providers/coach_chat_provider.dart (1)
278-435: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftExtract the agent loop from this oversized notifier.
This provider is now 575 lines and mixes state mutation, streaming protocol handling, tool execution, and prompt construction. Move the agent-loop orchestration into a focused collaborator/use case, leaving the notifier responsible for Riverpod state updates.
As per coding guidelines, “Keep hand-written source files to roughly 250 lines or less.” <coding_guidelines>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/features/coach_chat/providers/coach_chat_provider.dart` around lines 278 - 435, The oversized notifier should no longer own agent-loop orchestration. Extract _streamAgenticAnswer and _buildToolFollowUpPrompt, including streaming, tool-step coordination, retry limits, and prompt construction, into a focused collaborator or use case; keep _appendAssistantContent, _setAssistantContent, and _setAssistantToolSteps in the notifier for Riverpod state updates, and wire the collaborator to report stream/content/tool-step updates through a suitable callback interface.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/domain/entities/coach/coach_tool_call.dart`:
- Around line 4-29: Replace the manually implemented CoachToolCall model and
related tool-contract models with typed `@freezed` models, changing JSON-like
fields from Map<String, dynamic> to Map<String, Object?>. Remove custom equality
and hash-code helpers such as _mapEquals, and rely on Freezed-generated
immutable collection equality and model behavior across the affected contracts.
In `@lib/domain/usecases/coach/coach_tool_context_queries.dart`:
- Around line 19-40: Update the context formatting logic after the initial
availability check so each field is gated by its owning CoachDataSource
permission: add ACWR only when readinessAcwr is enabled, weekly momentum only
when weeklyMomentum is enabled, and adjustment hints only when their
corresponding training-insights permission is enabled. Preserve the existing
formatting and initial disabled-result behavior.
- Around line 116-125: Update computePacePlan to parse distance_km without the
5.0 fallback and validate it alongside target_time_minutes. Return the existing
error result when either required value is missing or non-positive, while
preserving the current range validation and pace-plan flow for valid inputs.
In `@lib/domain/usecases/coach/coach_tool_health_queries.dart`:
- Around line 112-128: Validate the normalized metric in the coach health query
flow before calling _health.getSummaries, accepting only hrv, rhr, sleep, and
steps; return the existing error result for unsupported values. Update the
validation in the method containing the shown metric handling so _metricValue
and _metricUnit never receive an unknown metric and cannot report HRV under
another label.
In `@lib/domain/utils/coach_tool_call_parser.dart`:
- Around line 14-18: Update CoachToolCallParser.tryParse and its related
detection logic to accept a TOOL_CALL marker only when it begins at the first
non-whitespace position, matching isDefinitelyNotToolCall’s prefix rule. Ensure
embedded directives are rejected rather than parsed, preventing them from
entering the streaming path; preserve valid leading-whitespace tool calls.
In `@lib/features/coach_chat/providers/coach_chat_provider.dart`:
- Around line 370-375: Update the multi-step tool-resolution flow around
_buildToolFollowUpPrompt so each iteration preserves previously generated
tool-result summaries or appends equivalent synthetic tool-result messages to
the follow-up history. Ensure the final inference receives results from every
tool execution, not only the most recent one, while retaining the existing
final-attempt behavior.
- Around line 368-377: Prevent another inference from starting after
cancellation in the tool-step loop. In the flow around _setAssistantToolSteps
and executeTool, check _cancelRequested immediately after updating the tool
status and exit the current operation before building the follow-up prompt; also
check it at the top of each iteration so cancellation is honored before any
sendCoach request.
In `@lib/infrastructure/coach/coach_conversation_codec.dart`:
- Around line 178-185: Update CoachConversationCodec._toolStepFromMap so
unrecognized non-null status strings do not make deserialization throw. Safely
resolve the mapped value against CoachToolStatus.values and fall back to
CoachToolStatus.success when lookup fails, while preserving the existing success
fallback for null values.
---
Nitpick comments:
In `@CODEMAP.md`:
- Line 271: Update the codemap generator’s layer-map table emission in
generate_codemap.dart to wrap the exported-symbols column value in Markdown
backticks, preserving the listed Dart symbol names while preventing underscores
from being parsed as emphasis markers.
In `@lib/features/coach_chat/providers/coach_chat_provider.dart`:
- Around line 278-435: The oversized notifier should no longer own agent-loop
orchestration. Extract _streamAgenticAnswer and _buildToolFollowUpPrompt,
including streaming, tool-step coordination, retry limits, and prompt
construction, into a focused collaborator or use case; keep
_appendAssistantContent, _setAssistantContent, and _setAssistantToolSteps in the
notifier for Riverpod state updates, and wire the collaborator to report
stream/content/tool-step updates through a suitable callback interface.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: b3f7a756-b7dc-4923-9ff2-b810cb1a9a54
📒 Files selected for processing (25)
CODEMAP.mdlib/domain/entities/chat_message.dartlib/domain/entities/coach/coach_tool_call.dartlib/domain/entities/coach/coach_tool_definition.dartlib/domain/usecases/coach/coach_tool_context_queries.dartlib/domain/usecases/coach/coach_tool_health_queries.dartlib/domain/usecases/coach/execute_coach_tool_usecase.dartlib/domain/utils/coach_tool_call_parser.dartlib/domain/utils/coach_tool_result_helpers.dartlib/features/coach_chat/presentation/widgets/agent_tool_step_list.dartlib/features/coach_chat/presentation/widgets/assistant_bubble.dartlib/features/coach_chat/presentation/widgets/message_list.dartlib/features/coach_chat/providers/coach_chat_provider.dartlib/features/coach_chat/providers/coach_chat_provider.g.dartlib/features/dashboard/presentation/pages/dashboard_page.dartlib/features/dashboard/presentation/widgets/coach_agent_hero_card.dartlib/infrastructure/ai/gemma/gemma_inference_session.dartlib/infrastructure/ai/hybrid_ai_coach_repository.dartlib/infrastructure/coach/coach_conversation_codec.dartlib/shared/providers/coach_usecase_providers.darttest/domain/usecases/coach/execute_coach_tool_usecase_test.darttest/domain/utils/coach_tool_call_parser_test.darttest/features/coach_chat/agent_tool_step_list_test.darttest/features/coach_chat/coach_chat_agentic_tool_test.darttest/features/dashboard/dashboard_page_test.dart
Convert coach tool models to @freezed with Map<String, Object?>, gate training-load fields by data-source permissions, tighten pace-plan and health-metric validation, reject embedded TOOL_CALL parsing, accumulate multi-step tool results with cancellation checks, and harden codec enum deserialization. Co-authored-by: Youri Bontekoe <YKDBontekoe@users.noreply.github.com>
# [1.18.0](v1.17.0...v1.18.0) (2026-07-13) ### Features * **coach:** make the AI coach agentic and the app's home-screen core ([#96](#96)) ([cce79a5](cce79a5))
Summary
Makes the AI coach genuinely agentic and puts it at the center of the app's home screen, per the KYNOS product vision of an AI-first running coach.
Agentic tool calling — the coach can now reason over runner data on demand instead of relying only on a fixed context dump that gets truncated for token budget:
get_recent_runs,get_run_detail(per-km splits),get_health_trend,get_training_load,get_character_progress,get_personal_bests,compute_pace_planCoachChat._streamAgenticAnswer: the model emits aTOOL_CALL: {...}directive, the app executes it and feeds the result back, up to 2 tool calls per turn before it's forced to answer directlyCoachDataSourceprivacy permissions used for the static context — tool calling can never surface more than the athlete already consented to share (Zero-Knowledge invariant, AGENTS.md §11)GemmaInferenceTier.fullto respect the tiny prompt budget on constrained devices; cloud always gets themAgentToolStepListchat UI shows which tools ran (running/success/error), so it's not a black boxHome screen redesign — added
CoachAgentHeroCardto the top of the Today dashboard: a live insight line plus one-tap prompts ("How's my training load?", "Plan my pace for a 10K", "Any personal bests lately?") that open Coach chat pre-filled, making the coach the app's primary entry point instead of a hidden modal.Testing
flutter analyze— zero issuesflutter test— 220/220 passing, including new coverage:coach_tool_call_parser_test.dart)ExecuteCoachToolUseCaseunit tests for all 7 tools, including a GPS-non-leakage checkProviderContainerintegration test exercising the realCoachChatnotifier + a scriptedAiCoachRepositoryfake, proving: aTOOL_CALLdirective is executed and the result is fed into the follow-up prompt; plain answers stream live with no tool steps; the loop stops after the tool budget and never leaks rawTOOL_CALLsyntaxflutter build web— succeedsbash scripts/check_design_system.sh/check_architecture.sh— passdart run tool/generate_codemap.dart— CODEMAP.md updatedflutter build web): confirmed the new Coach card renders correctly with all 3 prompt chips fully visible, and tapping a chip navigates into Coach chat with the question pre-filledkynos_coach_hero_card_final_demo.mp4
New "KYNOS Coach" hero card on the Today dashboard, with tap-through into Coach chat.
Note for reviewers
While testing in a real Chrome browser I found a pre-existing, unrelated bug: on wide/desktop web viewports the floating bottom nav bar scrolls with page content instead of staying fixed, eventually overlapping the header. I reproduced this before touching any code and confirmed it's unaffected by this PR's changes (it's in
ResponsiveCenter/ShellPage's web layout, not anything coach-related). Left out of scope here to keep this diff focused — flagging for a follow-up.Checklist
flutter analyze— zero issuesflutter test— zero failuresdart run tool/generate_codemap.dart— CODEMAP.md updatedbash scripts/check_design_system.sh— passesbash scripts/check_architecture.sh— passesflutter build web— succeedsTo show artifacts inline, enable in settings.
Summary by CodeRabbit