test(uipath-maestro-flow): grade read-only artifact checks after a turn timeout - #3541
rockymadden wants to merge 1 commit into
Conversation
…rn timeout coder_eval skips every run_command criterion after a terminal agent failure, so a timed-out run reports nothing about the artifacts it left on disk. UiPath/coder_eval#197 adds `read_only` as a per-criterion opt-in. Set it on the 71 criteria in smoke/hitl/ixp/interactive that only inspect files. Left unmarked: every `uip maestro flow validate` (tenant manifest fetch, and validate_flow.py retries a stalled fetch, so the verdict is not deterministic), everything reaching `flow debug`, check_ixp_handoff.py (`registry pull --force` plus tenant reads), and the two negative-only routing tasks, whose single criterion an untouched sandbox satisfies. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
CI: 🔴 1 red, and it is the block itself
That is Everything else is green: 32 pass, 6 skipped (draft-gated), 1 fail. The gates Locally, the same 33 files validate clean against #197's committed tree |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The pinned coder_eval 0.12.4 schema rejects read_only; upstream PR #197 and a compatible version update must land first.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: None
What changed in this PR
Adds post-timeout diagnostic grading for 71 deterministic, read-only Maestro Flow artifact checks without affecting task scores.
Changes:
- Marks eligible filesystem, JSON, and static flow checks as
read_only. - Covers smoke, HITL, IxP, and interactive tasks.
- Leaves validation, debug, and tenant-dependent checks unmarked.
| File | Description |
|---|---|
smoke/scheduled_trigger.yaml |
Marks scheduled-trigger inspection read-only. |
smoke/merge_parallel_sync.yaml |
Marks merge-topology inspection read-only. |
smoke/inline_agent_robust.yaml |
Marks agent-sidecar and flow checks read-only. |
smoke/init_validate.yaml |
Marks initialized-flow checks read-only. |
smoke/init_plain_flow_default.yaml |
Marks profile and artifact checks read-only. |
smoke/init_maestro_automate.yaml |
Marks Automate profile checks read-only. |
ixp/scaffold_multinode.yaml |
Marks multinode IxP checks read-only. |
ixp/scaffold_minimal.yaml |
Marks minimal IxP checks read-only. |
ixp/routing.yaml |
Marks IxP-node inspection read-only. |
ixp/integration_handle_routing.yaml |
Marks routing structure checks read-only. |
ixp/e2e_02_project_selection.yaml |
Marks model-selection checks read-only. |
ixp/e2e_01_invoice_extraction_greenfield.yaml |
Marks static grep checks read-only. |
interactive/solution_select.yaml |
Marks solution artifact checks read-only. |
interactive/slack_channel_description_simulated/slack_channel_description_simulated.yaml |
Marks Slack flow checks read-only. |
interactive/ixp_invoice_extraction_simulated/ixp_invoice_extraction_simulated.yaml |
Marks simulated IxP checks read-only. |
interactive/hitl_schema_design_simulated/hitl_schema_design_simulated.yaml |
Marks HITL schema checks read-only. |
interactive/expense_approval_simulated/expense_approval_simulated.yaml |
Marks expense-flow checks read-only. |
interactive/customer_escalation_simulated/customer_escalation_simulated.yaml |
Marks escalation artifact checks read-only. |
interactive/cli_dice_roller_simulated/cli_dice_roller_simulated.yaml |
Marks flow-name check read-only. |
interactive/bellevue_weather_simulated/bellevue_weather_simulated.yaml |
Marks flow-name check read-only. |
hitl/smoke_03_multi_outcome_routing.yaml |
Marks HITL routing checks read-only. |
hitl/smoke_02_completed_port_wired.yaml |
Marks outcome-wiring checks read-only. |
hitl/smoke_01_hitl_node_placed.yaml |
Marks HITL placement checks read-only. |
hitl/quality_04_brownfield_insert.yaml |
Marks brownfield preservation checks read-only. |
hitl/quality_03_boolean_decision.yaml |
Marks decision-output checks read-only. |
hitl/quality_02_result_downstream.yaml |
Marks downstream-result checks read-only. |
hitl/quality_01_schema_design.yaml |
Marks HITL schema checks read-only. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
nikhil-maryala
left a comment
There was a problem hiding this comment.
Overall decision: Comment (content ready; merge-gated on coder_eval #197)
Severity tally: 1 Blocker (merge precondition) · 0 Critical · 0 Major · 0 Minor · 1 Nit.
The classification is careful, and it matches the code. Verified:
- Uniform. 71 byte-identical
read_only: truelines, all onrun_commandcriteria, at the criterion-key indent. The two after a multi-linecommand:still parse as a top-leveltrue. None is oncommand_executed,llm_judge,skill_triggered, orjson_check. - Only pure readers are marked. I read every marked checker:
flow_contains,project_profile,check_simulated_hitl,check_inline_agent,check_escalation_behavior,check_merge_parallel_sync_flow,check_scheduled_trigger_flow,check_project_selection, andcheck_solution_selectin its read modes. None writes or spawns a subprocess, and theflow_checkhelpers they reach are pure. - Correctly left unmarked:
validate_flow.pyand inlinevalidate(tenant-dependent),check_solution_select validate, the three*_simulated.pycheckers that callrun_debug(a cloud job that rewrites the.uipx), and theregistry pull --forcecheckers. - Plumbing. All 134 flow YAMLs
safe_load;check-cli-verbs0 High/0 Medium;check-task-host-pathsOK. All required checks are green; the only red is the advisory schema check explained inline.
Follow-ups (outside the diff, optional):
tests/README.md§run_commanddoesn't documentread_only, or the rule never to markvalidate/debuggraders.- About 30 other flow tasks use the same pure checkers but aren't marked (
connector_features/*,evaluate/*,voice/*,multi_node/billing_*, …). That's harmless, but the scope (hitl/interactive/ixp/smoke) is worth stating or tracking.
Will approve once the pin is bumped to a release containing #197.
What this fixes
coder_evalrefuses to evaluate anyrun_commandcriterion after a terminal agentfailure:
The whole
uipath-maestro-flowsuite grades viarun_command, so a turn timeoutreports nothing at all about the artifacts sitting on disk. #197 adds
read_onlyas a per-criterion opt-in; this PR sets it on the criteria that genuinely qualify.
Scope check: the post-failure result is diagnostic.
post_failure_criteria_resultsis a separate field from
success_criteria_results— it does not feedcalculate_weighted_score, task gating, or thesuite_thresholdsrollups(
reports/markdown.pyaggregatessuccess_criteria_resultsonly). A timed-out runstill scores 0.00 and still reports ERROR. What changes is that the report now says
what the artifacts were worth, instead of silence.
The two proven false zeros
Both runs ended
final_status: ERROR,weighted_score: 0.0on a turn timeout.Every grader exits 0 against the preserved sandbox:
Classification
Every checker was read before classifying — no filename heuristics.
71 of 102
run_commandcriteria marked, across 27 files._shared/flow_contains.py(44)flow_check._rglob_pruned+open()/re.search. No subprocess, no writes._shared/project_profile.py(2)rglob+json.loads(operate.json). Pure._shared/check_simulated_hitl.py(9)find_flow_file+json.loads, node-shape asserts only._shared/check_inline_agent.py(3)glob+jsonoveragent.json. Stdlib only._shared/check_escalation_behavior.py(1).flowsource; the simulated twin of a debug checker, norun_debug.smoke/check_merge_parallel_sync_flow.py(1).flow.smoke/check_scheduled_trigger_flow.py(1).flow.ixp/check_project_selection.py(1).flow, matchesmodelNameagainst a literal table keyed on cwd.interactive/check_solution_select.py—flow,project,solution,existing-untouched,no-extra-solution(5).uipxandproject.uiproj.ixp/e2e_01inlinegrep -rq … --include="*.flow" .(4)_shared/validate_flow.py(22)ixp/e2e_01inlineuip maestro flow validate(1)interactive/check_solution_select.py validate(1)subprocess.run(["uip", …, "validate"])._shared/check_weather_flow_simulated.py,check_dice_runs_simulated.py,check_channel_description_simulated.py(3)flow_check.run_debug→uip maestro flow debugstarts a real cloud job.run_debugalso calls_rotate_solution_id, which rewrites the.uipxon disk and appends to a sidecar.interactive/customer_escalation_triage/check_customer_escalation_triage.py(1)run_debugpath, with grader-supplied inputs._shared/check_ixp_handoff.py(1)uip maestro flow registry pull --force(mutates the local registry cache, hits the tenant) plusuip ixp deployments list, and reads a.grader_baseline.jsonsnapshot.ixp/routing_listing.yaml,ixp/routing_negative.yaml(2)command_executed/command_not_executed/skill_triggered(25) are nottouched: they subclass
LiveSuccessCriterion, notRunCommandCriterion, sothey never reach this code path and carry no
read_onlyfield. They grade thetrajectory, which is exactly what a timeout truncates.
json_check(4) alreadyhas
supports_post_failure_evaluation = Trueat the class level.On
uip maestro flow validate— not markedIt is not a mutation:
validatewrites nothing server-side. It fails thedeterminism leg, and that is enough.
validaterefreshes the node manifest from the tenant on every invocation.validate_flow.py's own implementation is the evidence: it budgets the call,caps each attempt, retries once with a 5s backoff, and documents the fetch at
"8-14s typical and 60-67s on the tail". On exhaustion it returns 1 with
"The flow file itself may well be valid — this is the CLI's manifest fetch, not a
schema fault." Same artifacts, two verdicts, decided by tenant latency. A
criterion whose answer depends on a network round-trip is not the
"deterministic, read-only artifact check" the field is for.
Second reason to stay out: post-failure grading fires on runs that just blew their
time budget. Marking 22 criteria would add a tenant round-trip apiece to precisely
the runs already under load.
This is the conservative call by design — a missed opportunity costs nothing, a
wrongly-marked criterion is a correctness bug in the grader. The 49 pure checks
already recover the signal these 22 would have added.
Findings from my own adversarial pass
ixp/routing_listing.yaml(
flow_contains.py --expect-none) andixp/routing_negative.yaml(
--absent-regex uipath\.ixp) each have exactly one gradable criterion, and anuntouched sandbox satisfies it. Marked, a turn timeout would record "the agent
correctly refrained from building" for a run that died before it could — the
same false signal this PR exists to remove, inverted. Fixed: both unmarked.
Negative criteria that sit beside a positive one in the same task
(
ixp/e2e_02,interactive/solution_select) stay marked; the report cannot bemisread there unless the positives pass too.
sandbox.template_sourcesand_setupunder these four directories. The onlyseeded artifacts are
interactive/fixtures/existing_solutions/{SolarReports,TideTracker}.uipx(empty
Projectsarrays). No.flowis ever seeded, andhitl/quality_04_brownfield_inserthas the agent build its own base flow fromthe prompt. Every marked positive still requires agent work. No change needed.
;,&&,>,$(, backticks,rm,mkdir,cd. Only hits are|inside quoted regexalternations. Clean.
.flowraisesJSONDecodeErrorand thechecker exits non-zero with the filename. That is a correct fail, and identical
on every run against the same bytes. Determinism holds.
through
TaskDefinition.model_validateon feat(aops-governance): add uipath-aops-governance skill [PLT-100543] #197's committed tree(
24b2ab65), andRunCommandCriterion(read_only=True).evaluable_after_agent_failureis
True.Checks run
TaskDefinition.model_validateon all 33 yaml, against coder_eval #197 @24b2ab65valid=33 invalid=0 marked_run_command=71pytest tests/tasks/uipath-maestro-flow/{_shared,smoke}1199 passed in 5.75sscripts/check-task-host-paths.py --base-ref origin/main(task-host-paths-gate)OK — no $TASK_DIR/$SKILLS_REPO_PATH referencesscripts/check-task-driver.py tests/tasks tests/experiments(task-driver-gate)lint-tasks.ymlis draft-gated and advisory; it will fire when this comes out ofdraft.
Diff
71 added lines, all of them
read_only: true. No re-weighting, no re-wording, nocomment touched —
git diff -U0contains no other changed line.🤖 Generated with Claude Code