fix(run): address gadfly review of the phases PR
executus CI / test (pull_request) Successful in 48s
executus CI / test (pull_request) Successful in 48s
Real findings from the consensus review (37 raw; many devstral dups/noise): - Optional/budget-salvage branches no longer swallow a context cancellation / deadline / critic-kill: such errors return immediately so the run is classified cancelled/timeout/killed, not "ok" with a fallback. (the most serious finding — an Optional final phase could mask a killed run) - IsRunFunc bare phase now feeds the SHARED step observer (not just the audit recorder), so the critic's activity clock + Result.Steps see it — a long synthesize phase no longer looks idle to the critic. - phaseModel returns the resolver's enriched (usage-attribution) context and the phase's calls use it, mirroring the single-loop path (non-base-tier phases were mis-attributed). - salvagePhaseTranscript trims the tail on a rune boundary (was a raw byte slice that could split a UTF-8 rune); maxSalvage is now a named const with rationale. - expandPhaseTemplate logs a WARN on parse/execute failure instead of silently returning the unexpanded template; documented the phase-name identifier requirement + the "Query" shadow. - removed the dead phaseDeps.baseTier field. - extracted multimodalUserMessage, shared by runAgent + the phase runner (was duplicated image-folding). - aggregated phase usage is stamped onto the result even on a hard-error return; TrimSpace computed once; filterToolbox returns the base toolbox as-is for the empty-names (full-palette) case instead of copying; phaseModel WARN no longer prints error=<nil>. New test: Optional phase does not swallow a cancellation. Full ./... green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,31 @@ func TestPhases_OptionalFailureSubstitutesFallback(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestPhases_OptionalDoesNotSwallowCancellation: an Optional phase that fails
|
||||
// with a context cancellation must NOT be swallowed into its FallbackMessage —
|
||||
// the run genuinely ended (cancel/deadline/critic-kill) and must surface the
|
||||
// error so the run is classified cancelled/timeout/killed, not "ok".
|
||||
func TestPhases_OptionalDoesNotSwallowCancellation(t *testing.T) {
|
||||
models, _ := phaseProvider(t, fake.Fail(context.Canceled))
|
||||
ex := New(Config{Registry: tool.NewRegistry(), Models: models})
|
||||
|
||||
ra := RunnableAgent{
|
||||
Name: "pipeline",
|
||||
ModelTier: "test-model",
|
||||
Phases: []Phase{
|
||||
// IsRunFunc so the cancellation surfaces directly wrapped (%w).
|
||||
{Name: "a", SystemPrompt: "Phase A", IsRunFunc: true, Optional: true, FallbackMessage: "FB"},
|
||||
},
|
||||
}
|
||||
res := ex.Run(context.Background(), ra, tool.Invocation{RunID: "r", CallerID: "c"}, "Q")
|
||||
if !errors.Is(res.Err, context.Canceled) {
|
||||
t.Fatalf("Optional phase must NOT swallow a cancellation; res.Err = %v", res.Err)
|
||||
}
|
||||
if res.Output == "FB" {
|
||||
t.Error("a cancelled run must not report the fallback message as output")
|
||||
}
|
||||
}
|
||||
|
||||
// TestPhases_HardErrorAborts: a NON-optional phase that hits a hard error (not a
|
||||
// budget/step exhaustion) aborts the pipeline; later phases do not run.
|
||||
func TestPhases_HardErrorAborts(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user