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:
2026-07-02 00:56:45 +04:00
parent 968cbfa973
commit 18cbd34739
13 changed files with 296 additions and 24 deletions
@@ -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? =