fix(kernel,tools,workflow): session-robustness QA sweep
Uncommitted work from the session-robustness-and-dox branch sweep
(docs/qa/QA-session-robustness-and-dox.md), verified alongside the
compression/context fixes:
- SandboxedToolExecutor: validate tool args centrally before dispatch. A
malformed/missing-arg call becomes a recoverable ERROR: (surfaced with the
tool's arg schema so the model can correct + retry) instead of stranding
the stage with no artifact. + validation test.
- PlanLinter: seed artifacts (analysis) produced by the planning phase count
as available producers, so a plan stage that `needs` them isn't flagged as
an unproduced-need; H1 unproduced-needs + trap-state checks. + tests.
- DefaultSessionOrchestrator: live-QA robustness fixes (event-tail /
per-stage budget + retry handling).
- workflow prompts/configs: DOX AGENTS.md alignment + freestyle/task/role
prompt tweaks.
- SessionOrchestratorIntegrationTest: coverage for the above.
- FreestylePlanningWorkflowTest: allow list_dir in analyst tools (follows the
list_dir wiring in 968cbfa).
- QA plan doc for the branch sweep.
This commit is contained in:
+36
-10
@@ -170,11 +170,13 @@ class DefaultSessionOrchestrator(
|
||||
is TransitionDecision.Stay -> if (isTerminal(enriched.graph, enriched.currentStageId)) {
|
||||
completeWorkflow(enriched.sessionId, enriched.currentStageId, enriched.stageCount)
|
||||
} else {
|
||||
failWorkflow(
|
||||
sessionId = enriched.sessionId,
|
||||
stageId = enriched.currentStageId,
|
||||
reason = "no transition condition matched from stage ${enriched.currentStageId.value}",
|
||||
retryExhausted = false,
|
||||
// No outgoing edge matched: the stage finished but produced nothing that satisfies a
|
||||
// transition (e.g. the analyst never emitted a validated `analysis`). Treat it as a
|
||||
// retryable stage failure — a fresh attempt lets the model gather more before writing —
|
||||
// rather than killing the run. Only terminal once the retry budget is spent.
|
||||
retryStageOrFail(
|
||||
enriched,
|
||||
"no transition condition matched from stage ${enriched.currentStageId.value}",
|
||||
)
|
||||
}
|
||||
|
||||
@@ -185,15 +187,39 @@ class DefaultSessionOrchestrator(
|
||||
retryExhausted = false,
|
||||
)
|
||||
|
||||
is TransitionDecision.NoMatch -> failWorkflow(
|
||||
sessionId = enriched.sessionId,
|
||||
stageId = enriched.currentStageId,
|
||||
reason = "no matching transition from stage ${enriched.currentStageId.value}",
|
||||
retryExhausted = false,
|
||||
is TransitionDecision.NoMatch -> retryStageOrFail(
|
||||
enriched,
|
||||
"no matching transition from stage ${enriched.currentStageId.value}",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A non-terminal stage whose outcome matched no outgoing transition (Stay/NoMatch) produced no
|
||||
* usable artifact. Route it back through the stage's retry budget instead of failing the run:
|
||||
* re-execute the same stage if attempts remain, else fail terminally with retryExhausted=true.
|
||||
*/
|
||||
private suspend fun retryStageOrFail(
|
||||
ctx: EnrichedExecutionContext,
|
||||
reason: String,
|
||||
): WorkflowResult {
|
||||
val attempt = orchestrationRepository.getState(ctx.sessionId).retryCount
|
||||
val shouldRetry = retryCoordinator.shouldRetry(
|
||||
sessionId = ctx.sessionId,
|
||||
stageId = ctx.currentStageId,
|
||||
currentAttempt = attempt,
|
||||
policy = ctx.config.retryPolicy,
|
||||
failureReason = reason,
|
||||
)
|
||||
if (!shouldRetry) {
|
||||
return failWorkflow(ctx.sessionId, ctx.currentStageId, reason, retryExhausted = true)
|
||||
}
|
||||
return when (val r = enterStage(ctx, ctx.currentStageId)) {
|
||||
is StepResult.Continue -> step(r.ctx)
|
||||
is StepResult.Terminal -> r.result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Returns the cached validated artifact content for the given session + artifact id, or null if absent. */
|
||||
fun validatedArtifactContent(sessionId: SessionId, artifactId: ArtifactId): String? =
|
||||
|
||||
Reference in New Issue
Block a user