test(flow/hitl): path-agnostic criteria for solution-wrapped flow projects - #2534
test(flow/hitl): path-agnostic criteria for solution-wrapped flow projects#2534nikhil-maryala wants to merge 2 commits into
Conversation
…jects Agents that scaffold via `uip solution init` produce <Name>Solution/<Name>/<Name>.flow while 17 greenfield task YAMLs hardcoded the flat <Name>/<Name>/<Name>.flow in file_exists / file_contains / file_matches_regex / run_command criteria — a correct flow scored 0.0 purely on the path (nightlies: 14 delegate + 4 claude + several antigravity false negatives). The migrated hitl smoke tasks already established the fix pattern; this applies it to the remaining floating tasks. - flow_contains.py: add repeatable `--regex PATTERN` (path-agnostic replacement for file_matches_regex) + unit tests - 61 criteria across 17 task YAMLs migrated to validate_flow.py / flow_contains.py discovery; weights, thresholds, and the quality_08 negative-check contract (expected_exit_code: 1) preserved - Tasks whose prompts pin the literal path (brownfield fixtures, inline_agent_robust) are intentionally untouched Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @nikhil-maryala's task in 1m 26s —— View job Coder-eval task lint (advisory)
|
|
Claude finished @nikhil-maryala's task in 2m 9s —— View job PR Review: test(flow/hitl): path-agnostic criteria for solution-wrapped flow projects
SummaryMigrates 61 success criteria across 17 task YAMLs (10 HITL, 7 maestro-flow) from hardcoded Change-by-Change Review1.
|
| Area | Status | Notes |
|---|---|---|
| Frontmatter | N/A | No skill frontmatter changes |
| E2E Tests | OK | Criteria migrated correctly, weights/thresholds preserved |
| Skill Body | N/A | No skill doc changes |
| References & Assets | OK | Shared helper extended cleanly with tests |
| Repo Hygiene | OK | No secrets, no broken links, scoped changes |
Issues for Manual Review
- Verify whether the
run_limitsremoval fromscaffold_multinode.yamlwas intentional. The prior limits (max_turns: 40) were significantly tighter than the experiment default (max_turns: 200). - The PR description mentions dispatched coder-eval runs but results are pending — confirm those pass before merging.
Conclusion
Clean, well-scoped mechanical migration that follows the established pattern from #2213. The flow_contains.py extension is minimal and well-tested. One Medium finding: the run_limits removal from scaffold_multinode.yaml appears accidental and should be either restored or justified.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a5c251a24
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - type: run_command | ||
| description: "Flow file was created at the expected path" | ||
| path: "InvoiceApproval/InvoiceApproval/InvoiceApproval.flow" | ||
| command: "python3 $SKILLS_REPO_PATH/tests/tasks/uipath-maestro-flow/_shared/flow_contains.py" |
There was a problem hiding this comment.
Scope discovery to the requested flow name
When an agent creates any lone Flow project, this criterion succeeds regardless of its name because flow_contains.py discovers an arbitrary Flow project without receiving the expected InvoiceApproval basename. A correctly shaped Foo.flow can therefore earn all artifact and validation points while ignoring the prompt's naming requirement that the previous literal path enforced; keep the wrapper directory flexible but require the expected project/flow basename.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0b9d18a — every migrated invocation now passes --flow-name <Name> (names recovered from the original literal paths), so a lone Foo.flow no longer earns the points: discovery stays wrapper-agnostic but the basename must match. Covered by test_flow_name_mismatch_exits_1 / test_flow_name_scopes_assertions.
| missing = [s for s in substrings if not any(s in body for body in bodies.values())] | ||
| for s in substrings: | ||
| print(f"{'MISSING' if s in missing else 'OK '} {s}") | ||
| if missing: | ||
| missing_re = [ | ||
| p for p in regexes if not any(re.search(p, body) for body in bodies.values()) |
There was a problem hiding this comment.
Match each assertion set within one flow file
When a project contains multiple flows or subflows, each requested substring or regex is searched independently across all files, so no single flow needs to satisfy the criterion. For example, the migrated scaffold_multinode.yaml check for postReceipt, sendToValidation, and logError passes if those names are split among three valid flows even though the task requires all three nodes in the authored ReceiptIntake flow; require one discovered body to satisfy the complete assertion set.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0b9d18a — positive assertion sets (substrings + --regex) must now be satisfied by ONE discovered file; split-matching across subflows fails with FAIL: no single .flow file satisfies the full assertion set. Combined with --flow-name ReceiptIntake on the scaffold_multinode criteria, the exact scenario you describe now scores 0. Covered by test_assertion_set_must_match_single_file.
| command: "python3 $SKILLS_REPO_PATH/tests/tasks/uipath-maestro-flow/_shared/flow_contains.py --regex '\\.output\\.(legalApproval|legalNotes)'" | ||
| timeout: 30 | ||
| expected_exit_code: 1 |
There was a problem hiding this comment.
Distinguish successful absence from helper failure
When the agent creates no .flow file, discovery is ambiguous, or reading a flow fails, flow_contains.py exits 1—the same code this negative criterion treats as success. Because criteria are scored independently, the other existence checks do not prevent an invalid submission from receiving these 2.5 points; use an explicit absence assertion that returns success only after project discovery and file reads complete without the forbidden pattern.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0b9d18a — added an explicit --absent-regex mode that exits 0 only when discovery succeeded, the --flow-name ContractReview file was read, and the forbidden pattern is absent. quality_08's negative check now uses it with expected_exit_code: 0, so a missing/ambiguous flow can never collect the 2.5 points. Covered by test_absent_regex_fails_without_flow.
|
Passing-run claim —
Antigravity attribution (all 5 failures are the flash-tier authoring collapse these tasks already show in the nightlies — none are caused by this migration):
|
…sence Review follow-up (#2534), addressing all three findings: - P1: every migrated invocation now passes --flow-name <Name> (recovered from the original literal paths), restoring the basename enforcement the hardcoded paths had while staying wrapper-agnostic. - P2: positive assertion sets (substrings + --regex) must now be satisfied by ONE discovered file — a project with subflows can no longer pass by splitting assertions across files. - P3: new --absent-regex mode exits 0 only after discovery and reads succeed with the forbidden pattern absent; quality_08's negative check migrated to it (expected_exit_code: 0), so a missing flow can never score the negative criterion. 83/83 unit tests pass, including new coverage for all three contracts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0b9d18a3a8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| command: "python3 $SKILLS_REPO_PATH/tests/tasks/uipath-maestro-flow/_shared/flow_contains.py --flow-name ReceiptIntake '$vars.'" | ||
| timeout: 30 | ||
| expected_exit_code: 0 | ||
| weight: 1.5 | ||
| pass_threshold: 1.0 |
There was a problem hiding this comment.
Restore the task's custom run limits
Restore the run_limits block after this migrated criterion. Without it, ixp-scaffold-multinode inherits the default experiment's max_turns: 200 and task_timeout: 1200 instead of its previous 40-turn/900-second limits, allowing substantially longer and more expensive runs and changing pass-rate comparability for a commit intended only to make artifact paths flexible.
Useful? React with 👍 / 👎.
|
Re-verification after the review-round commit (
Tasks: |

Problem
Agents that scaffold via
uip solution initproduce<Name>Solution/<Name>/<Name>.flow, but 17 greenfield task YAMLs hardcode the flat<Name>/<Name>/<Name>.flowinfile_exists/file_contains/file_matches_regex/run_commandcriteria. A correct, validating flow scores 0.0 purely on the path. Nightly evidence: the 7/30 delegate run's ownanalysis.mddocuments 14 HITL/flow tasks failing exactly this way; the 7/31 claude run flagged 4 more (hitl-quality-result-downstream,hitl-quality-priority-timeout,paginated-reference-lookup,ixp-scaffold-multinode); antigravity runs show the same signature asvalidate: File not found. Because the wrapper choice is stochastic per model, this mismatch manufactures fake cross-model signal.The already-migrated
hitl/smoke_01..03tasks established the fix pattern (#2213'svalidate_flow.py+flow_contains.pydiscovery); this PR applies it to the remaining floating tasks.Changes
_shared/flow_contains.py: add repeatable--regex PATTERN— the path-agnostic replacement forfile_matches_regex. Newtest_flow_contains.pycovers substring/regex present/absent, the negative-check exit-1 contract, and dangling-flag exit 2 (75/75 pass locally).validate_flow.py/flow_contains.pydiscovery. Descriptions, weights, andpass_thresholds preserved verbatim.quality_08_variable_binding_fieldid's negative check (script must NOT reference.output.legalApproval|legalNotes) keeps its inverted contract:expected_exit_code: 1with a comment explaining the semantics.Intentionally untouched: tasks whose prompts pin the literal path (
e2e_02/04/05/07brownfield fixtures,inline_agent_robust) — there the hardcoded path is the contract, not a bug.Verification
tests/tasks/uipath-maestro-flow/_shared/(CI: test-helpers.yml).run-coder-eval.ymldispatched from this branch on the evidence-backed regressing tasks across claude / codex / antigravity — run links + results to follow in a comment. Note the wrapper choice is stochastic: green runs prove no regression on both layouts; the unit tests prove the discovery works for the wrapped layout deterministically.🤖 Generated with Claude Code