fix(kernel): require tool activity for produce-less stage completion

A stage declaring no `produces` passed verifyProduces vacuously, letting an
agent stage_complete with zero work. Gate on allowedTools: a produce-less stage
with tools available must have >=1 ToolInvocationRequestedEvent scoped to its
stageId, else Failure(retryable). Produce-less + no tools is exempt (only
stage_complete is ever offered), so pure-reasoning stages aren't bricked.
This commit is contained in:
2026-06-02 19:56:34 +04:00
parent 1dc7eae8a1
commit d1dc9e2f5c
2 changed files with 190 additions and 1 deletions
@@ -705,7 +705,21 @@ abstract class SessionOrchestrator(
stageId: StageId,
stageConfig: StageConfig,
): StageExecutionResult {
if (stageConfig.produces.isEmpty()) return StageExecutionResult.Success(emptyList())
if (stageConfig.produces.isEmpty()) {
if (stageConfig.allowedTools.isEmpty()) return StageExecutionResult.Success(emptyList())
val ranTool = eventStore.read(sessionId).any {
(it.payload as? ToolInvocationRequestedEvent)?.stageId == stageId
}
if (ranTool) return StageExecutionResult.Success(emptyList())
log.error(
"[Orchestrator] stage declared no artifacts and ran no tools " +
"session=${sessionId.value} stage=${stageId.value}",
)
return StageExecutionResult.Failure(
"stage ${stageId.value} declared no artifacts and ran no tools",
retryable = true,
)
}
val producedIds = stageConfig.produces.map { it.name }.toSet()
val present = eventStore.read(sessionId)
.mapNotNull { (it.payload as? ArtifactCreatedEvent)?.artifactId }