feat(workflow): tail-only loop break, final-step outcome report, artifact handoff - #141
Merged
Conversation
…fact handoff Restores three workflow behaviors and does each the ADK-native way. 1. Only the tail step of a loop can break out early. exit_loop is now attached to the last inner step only, not every inner step. It sets Actions.Escalate — the only way an ask tool ends a loopagent — and no other tool touches Actions, so withholding it makes an early break structurally impossible for the non-tail steps. This replaces the old end_turn decision guard, which detected the violation after the fact and re-prompted; now it cannot happen. The non-tail loop instruction is reworded to say the step cannot end the loop. 2. finish_workflow is attached again. The tool and the Progress capture both survived #136, but nothing attached the tool to a step once the graph became one session, so a run reported no artifacts. It is now attached to the final step (WorkflowStepTools). This is how the user learns what a run produced — PR links, tickets — so it is not optional. 3. Artifacts pass structured data between steps. Every step gets save_artifact (native, writes through ctx.Artifacts().Save — ADK ships only load_artifacts) and load_artifacts. This works now, where it did not before #136: the whole graph runs as one runner invocation, so the runner's ArtifactService spans every step. This is the ADK-native replacement for the ask/plans notes directories. Mechanics: CompileWorkflow hands each step a StepRole{InLoop, IsTail, IsFinal} through its ToolsBuilder. IsTail gates exit_loop, IsFinal gates finish_workflow. The TUI builder (cmd/ask/workflow_graph.go) appends tools.WorkflowStepTools directly; the headless builder passes WorkflowStep/WorkflowFinalStep flags to the tool factory, which appends them in BuildCoreTools (pkg/engine can't import pkg/tools). Tests: StepRole is computed correctly for linear/loop/final-loop shapes; WorkflowStepTools attaches save+load always and finish only on the final step; save_artifact writes name+content through ctx.Artifacts().Save and errors (a real Go error, so retryandreflect sees it) with no service or no name; the loop instruction tells only the tail step how to break, and only the final step to call finish_workflow. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A two-step workflow on the real ADK runner: step 1 saves an artifact, step 2 loads it. Proves the whole seam the earlier tests only covered in pieces — the tool factory attaches save_artifact/load_artifacts to every step, the graph runs as one runner invocation so the ArtifactService spans both steps, and the saved content reaches step 2's model. Lives in the external engine_test package so it can import pkg/tools (whose init registers the tool factory) without the tools -> engine import cycle. A scripted model.LLM drives one step at a time by the marker in each step's system instruction. The assertion is airtight against a false pass: the secret must be ABSENT from the reader's step handoff (the saver's node output is 'saved the plan', and IncludeContentsNone keeps its tool calls out of the reader's context) and PRESENT only after load_artifacts resolves. A negative control with a mismatched artifact name fails as expected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
Author
|
Added the real end-to-end test: A two-step workflow runs on the actual ADK graph — step 1 calls It's airtight against a false pass: the secret must be absent from the reader's step handoff (proving |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Restores three workflow behaviors that the graph migration (#136) dropped or weakened, each done the ADK-native way.
1. Only the tail step of a loop can break out
exit_loopwas attached to every inner step, so any step could end the loop. Now it's attached to the last inner step only.exit_loopsetsActions.Escalate— the only way an ask tool ends aloopagent— and no other tool touchesActions. So withholding the tool makes an early break structurally impossible for the non-tail steps. This is your oldend_turndecision guard, except the old one detected the violation after the fact and re-prompted; this one makes it unrepresentable. The non-tail loop instruction is reworded to say the step cannot end the loop.2.
finish_workflowis attached again (regression fix)The tool and the
Progresscapture both survived #136, but nothing attached the tool to a step once the graph became one session (the oldIsWorkflowFinalStepflag was per-session). So runs reported no artifacts to the user. It's now attached to the final step, which is how the user learns what a run produced — PR links, tickets. Not optional.3. Artifacts pass structured data between steps
Every step gets
save_artifactandload_artifacts. This works now where it didn't before #136: the whole graph runs as one runner invocation, so the runner'sArtifactService(fromRunnerBuilder) spans every step — a save in step 1 is visible to a load in step 3.ADK ships
load_artifactsbut no save tool, soSaveArtifactToolis the missing half:This is the ADK-native replacement for the old
ask/plans/notes directories.Mechanics
CompileWorkflowhands each step aStepRole{InLoop, IsTail, IsFinal}through itsToolsBuilder.IsTailgatesexit_loop,IsFinalgatesfinish_workflow. The TUI builder (cmd/ask/workflow_graph.go) appendstools.WorkflowStepToolsdirectly; the headless builder passesWorkflowStep/WorkflowFinalStepflags to the tool factory, which appends them inBuildCoreTools(pkg/engine can't import pkg/tools, hence the flag indirection).Tests
StepRoleis computed correctly for linear / loop / final-loop shapes (the tail of a final loop is bothIsTailandIsFinal)WorkflowStepToolsattaches save+load always, finish only on the final stepsave_artifactwrites name+content throughctx.Artifacts().Save(verified with a fake store), and returns a real Go error — soretryandreflectsees it — when there's no service or no namefinish_workflowmake testgreen across all 9 packages.Note
I did not touch
docs/— it was intentionally removed in3683f9d. All doc updates are in CLAUDE.md.🤖 Generated with Claude Code