Fix subagent terminal result summaries#1519
Conversation
| => result.Success ? result.Output : $"Subagent '{agent}' failed: {result.Output}"; | ||
| { | ||
| var builder = new StringBuilder(); | ||
| builder.AppendLine("Subagent run finished."); |
There was a problem hiding this comment.
This is the parent-facing behavior change for #1507: the tool result now starts with an explicit terminal marker instead of raw subagent prose, so the parent model has an unambiguous completion signal.
| Complete( | ||
| true, | ||
| text, | ||
| _forcedFinalOutcomeReason.HasValue ? SubAgentRunOutcome.Partial : SubAgentRunOutcome.Completed, |
There was a problem hiding this comment.
This line is what turns a forced no-tools wrap-up into a partial run instead of a normal completion. That distinction matters because tool-budget exhaustion can still produce a useful summary, but it should be visible to the parent and operators as a degraded terminal outcome.
| Shape = SubAgentFindingShape.Conclusion, | ||
| Title = $"subagent:{_definition.Name}", | ||
| Content = normalized, | ||
| Content = content, |
There was a problem hiding this comment.
This is the memory-side change: findings now preserve the full final output instead of blindly truncating it before review. The parent-facing spawn_agent result is still the terminal envelope; this content is only for the separate structured-finding path.
| return result; | ||
| return result with | ||
| { | ||
| RunId = runId, |
There was a problem hiding this comment.
This attaches the spawner-generated run id to the returned result, which is what lets the terminal tool envelope correlate with session output events and logs. Without this, the parent-facing diagnostics pointer would not be tied back to the actual child run.
| RunId = info.RunId, | ||
| AgentName = new SubAgents.AgentName(info.AgentName), | ||
| Success = info.Success, | ||
| Outcome = info.Outcome ?? (info.Success ? SubAgentRunOutcome.Completed : SubAgentRunOutcome.Failed), |
There was a problem hiding this comment.
This is the handoff from subagent runtime metadata into the session event model. It keeps completion events aligned with the terminal tool envelope, while still defaulting legacy success/failure notifications when older paths do not provide an explicit outcome.
| Phase = msg.Phase.ToString().ToLowerInvariant(), | ||
| ToolCountSub = msg.ToolCount, | ||
| SubAgentSuccess = msg.Success, | ||
| SubAgentOutcome = msg.Phase == SubAgents.SubAgentPhase.Completed |
There was a problem hiding this comment.
This intentionally emits outcome metadata only for completed subagent events. A started event has no terminal outcome yet, so leaving the DTO fields absent avoids sending a misleading completed default over SignalR.
Summary
Fixes the loop where the parent model receives a completed
spawn_agentresult as an undifferentiated text blob, misses that the subagent already finished the task, and delegates the same work again.This change makes every
spawn_agentcompletion return a clear terminal envelope with the subagent name, run id, outcome, optional reason, diagnostics pointer, and either aSummary:orError:section. A run that exhausts its tool-iteration budget but still returns a useful final answer is now markedpartialinstead of looking like an ordinary success or a hard failure.Structured findings remain available for the memory pipeline, but they are kept separate from the parent-facing tool result and no longer blind-truncate the subagent's final output.
Fixes #1507.
Validation
dotnet build Netclaw.slnx --no-restoredotnet test src/Netclaw.Actors.Tests --no-build --filter "FullyQualifiedName~SubAgent|FullyQualifiedName~TurnStateTracker"dotnet test src/Netclaw.Cli.Tests --filter "FullyQualifiedName~DaemonClientMappingTests"dotnet slopwatch analyzepwsh ./scripts/Add-FileHeaders.ps1 -Verify