Skip to content

fix(flows): add result.json as second success-confirmation channel - #234

Merged
d-mo merged 4 commits into
mainfrom
fix/success-confirmation-channels
Aug 17, 2026
Merged

fix(flows): add result.json as second success-confirmation channel#234
d-mo merged 4 commits into
mainfrom
fix/success-confirmation-channels

Conversation

@d-mo

@d-mo d-mo commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Problem: sentinel false negatives

The success sentinel is a deliberate fail-closed positive-confirmation contract (3902606): agent CLIs exit 0 even when the agent died mid-task, so exit code 0 is never sufficient for success. That ruling stands — this PR does not trust exit codes (unlike closed PR #232, which went the wrong direction).

The problem is false negatives: execution ff1294e1 (staging, internal id) completed a full PR review — review verifiably submitted via MCP, exit 0 — but forgot to print FLOW_EXECUTION_SUCCESS after a 3.7M-token run, and was marked FAILED.

Fix: redundant explicit confirmation channels

Success still requires an explicit act by the agent, now through either of two channels:

  1. Channel 1 (unchanged): print the FLOW_EXECUTION_SUCCESS sentinel in logs (armed only after PRELOOP_AGENT_EXEC_START).
  2. Channel 2 (new): write /workspace/result.json with a success status (success/succeeded/pass/passed — includes the preloop.eval.result/v1 vocabulary). Captured first-class via the artifact path merged in feat(flows): Observe / Eval preset with first-class result.json artifact #231.

Rules:

  • Either channel suffices. Neither present → FAILED (unchanged fail-closed behavior).
  • An explicit failure status in result.json wins over everything, including a printed sentinel (applies on both the terminal path and the sentinel grace-period path).
  • Nonzero exit remains FAILED regardless of any confirmation.

Hardening & diagnostics

  • The injected confirmation instruction stays at the very end of the resolved prompt (recency — most likely to be honored after multi-million-token runs) and now states the result.json alternative.
  • When a run fails only for missing confirmation (exit 0, no error heuristics), the error_message says so explicitly and names both channels, so operators can tell this class apart from real failures at a glance. New milestones: success_confirmation_missing, result_artifact_failure_override, result_artifact_confirmed_success.
  • Preset 002 (PR reviewer) gains a mandatory final step (Step 8.2) writing result.json with {status, risk_level, findings_count, review_action} as its completion act. No EE preset variant lives in this repo, so nothing to mirror here.

Tests

New TestSuccessConfirmationChannels covers the contract matrix:

  • exit 0 + neither channel → FAILED (contract regression guard; diagnostic names both channels)
  • exit 0 + sentinel → SUCCEEDED
  • exit 0 + result.json success → SUCCEEDED (artifact persisted)
  • result.json failure + sentinel → FAILED (explicit failure wins)
  • nonzero exit → FAILED despite both channels claiming success

backend/tests/test_flow_orchestrator.py (38 passed), preset/container suites (97 passed), and the full non-integration backend suite pass against a local dockerized Postgres (pgvector) after alembic upgrade head; the only failures are 13 pre-existing on clean main (auth/roles/issue-duplicates, unrelated). ruff check/format clean.

Note

Medium
Accepts result.json as a second success-confirmation channel; the prior fail-conflation concern is resolved, but the injected confirmation instruction now conflicts with the Observe/Eval preset's result.json contract.

Overview
Adds /workspace/result.json as a redundant success-confirmation channel alongside the printed FLOW_EXECUTION_SUCCESS sentinel, keeps nonzero-exit failure authoritative, treats eval pass/fail as completed-run verdicts (success confirmation), and adds a mandatory result.json step to the PR-reviewer preset, with a contract-matrix test suite.

Written by Preloop PR Reviewer for commit 4e15172. Updates automatically on new commits.

@cursor

cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@preloop-staging preloop-staging Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Preloop has suggestions for this PR. Some improvements are recommended. See the summary comment for details.

Comment thread backend/preloop/services/flow_orchestrator.py Outdated
Comment thread backend/preloop/services/flow_orchestrator.py Outdated
Comment thread backend/preloop/services/flow_orchestrator.py
@preloop-staging

preloop-staging Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

🔍 Preloop Code Review

Last Updated: 2026-08-17
Reviewing Commit: 1ca8f903
Review Status: ✅ Approved
Review Scope: Incremental since 4e15172


📝 Summary

The incremental delta (commit 1ca8f903) resolves the final open finding: prompt resolution now selects a contract-aware completion instruction, so Observe/Eval flows receive a structured-report-only instruction (no sentinel, no bare {"status": "success"}) instead of the review-vocabulary one. All previously raised issues are now addressed.

✅ What Looks Good

  • _success_instruction_for_prompt detects the eval contract via both the schema identifier preloop.eval.result/v1 and the preset's no-sentinel directive, selecting FLOW_EVAL_SUCCESS_INSTRUCTION, which preserves the structured report and maps pass/fail → completed, error → failed.
  • Detection is verified against the current preset set: only 003-observe-eval.yaml carries the eval vocabulary, so no non-eval flow is misrouted.
  • New parametrized tests cover both eval-detection signatures and the normal-flow path, asserting the eval instruction excludes both the sentinel and the bare success object.

✅ Resolved Issues

  • eval fail verdict conflated with flow failurefail moved to the success set; fixed in 408f34b9
  • result_artifact.get(...) on Optional[Dict] fails strict mypyassert result_artifact is not None guards added
  • grace-period failure-override path untested and omits exit_code/failure_analysis — now included and covered by test_grace_period_failure_override_includes_exit_code
  • injected FLOW_SUCCESS_INSTRUCTION conflicts with the Observe/Eval preset's result.json contract — contract-aware instruction selection added; fixed in 1ca8f903

Progress: 4 of 4 issues addressed

This summary updates automatically on each review. Inline comments provide detailed feedback on specific lines.

d-mo and others added 2 commits August 17, 2026 14:38
The sentinel is a deliberate fail-closed positive-confirmation contract
(3902606): agent CLIs exit 0 even when the agent died mid-task, so exit
code 0 is never sufficient for success. But the single channel produced
false negatives: execution ff1294e1 completed a full PR review (review
verifiably submitted via MCP, exit 0) yet was FAILED because the model
forgot to print FLOW_EXECUTION_SUCCESS after a 3.7M-token run.

Reduce false negatives without weakening the contract, via redundant
explicit confirmation channels:

- A /workspace/result.json artifact with a success status (including the
  preloop.eval.result/v1 "pass" vocabulary) now counts as positive
  confirmation, equal in standing to the printed sentinel. Either
  explicit act suffices; neither present -> FAILED (unchanged).
- An explicit failure status in result.json wins over everything,
  including a printed sentinel.
- The injected instruction stays at the very END of the resolved prompt
  (recency) and now states the result.json alternative.
- When a run fails ONLY for missing confirmation (exit 0, no error
  heuristics), the error_message says so explicitly and names both
  channels, so operators can tell this class apart from real failures.
- Preset 002 (PR reviewer) gains a mandatory final step writing
  result.json ({status, risk_level, findings_count, review_action}) as
  its completion act. (No EE preset variant lives in this repo.)

Tests cover the contract matrix: exit 0 + neither channel -> FAILED;
exit 0 + sentinel -> SUCCEEDED; exit 0 + result.json success ->
SUCCEEDED; result.json failure + sentinel -> FAILED; nonzero exit ->
FAILED despite both channels claiming success.
status=fail means the subject's checks failed but the eval finished.
Only error/failed/failure override the flow to FAILED. Grace-period
failure override now carries exit_code for retry decisions.

Co-authored-by: Cursor <cursoragent@cursor.com>
@d-mo
d-mo force-pushed the fix/success-confirmation-channels branch from d402d1b to 408f34b Compare August 17, 2026 11:44

@preloop-staging preloop-staging Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Preloop has suggestions for this PR. All three previously raised code findings are now resolved — nice work. Only a documentation gap remains (ARCHITECTURE.md still describes the result.json artifact contract as passive). See the summary comment for details.

Document the active status contract so operators can distinguish flow failures from customer-owned eval verdicts.

Co-authored-by: Cursor <cursoragent@cursor.com>
@d-mo

d-mo commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Review cleanup is complete on current HEAD 4e151727faaa921ff81849a021b0a549e6a15917.

  • The three code review threads are already resolved on this HEAD.
  • ARCHITECTURE.md now documents result.json as an active success-confirmation channel alongside FLOW_EXECUTION_SUCCESS: success confirms the flow, error / failed / failure fail it, and eval fail remains a subject verdict rather than a flow failure. Customer verifiers still own scoring.
  • Staging execution 6f519e5e-98c2-405f-8730-7e4ed1fb821c is not a product failure in this diff. The GitHub commit status reports: Preloop execution failed: Failed to start agent Job: (409) Reason: Conflict. This is the reviewer retry attempting to recreate the Kubernetes Job that the first reviewer run already created. All GitHub Actions checks are green. I could not fetch the staging console URL from this environment because the local browser runtime was unavailable, so the GitHub status is the directly fetched evidence here.

@preloop-staging preloop-staging Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💬 Preloop has suggestions for this PR. Some improvements are recommended. See the summary comment for details.

IMPORTANT: When you have successfully completed your task, you MUST print the following marker on a line by itself: {FLOW_SUCCESS_SENTINEL}
Do not include any other text on the same line as the marker. This signals successful completion.
IMPORTANT: When you have successfully completed your task, you MUST confirm success in one of two ways: print the following marker on a line by itself (no other text on that line): {FLOW_SUCCESS_SENTINEL}
or write /workspace/result.json containing {{"status": "success"}}. Without one of these confirmations the run is marked FAILED.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] [Quality]

Affected files:

  • backend/preloop/services/flow_orchestrator.py:81-86
  • backend/preloop/services/flow_orchestrator.py:866
  • backend/presets/003-observe-eval.yaml:40-44

FLOW_SUCCESS_INSTRUCTION is written in review vocabulary — write /workspace/result.json containing {"status": "success"} — and offers the sentinel as the alternative channel, but it is appended unconditionally to every resolved prompt (line 866), including Observe/Eval flows. The 003 preset tells its agent the opposite: result.json must use status: pass | fail | error (003-observe-eval.yaml:50), and it must "Do NOT print sentinel markers or paste the JSON into your chat output" (003-observe-eval.yaml:43).

Because the injected instruction is the last thing the agent sees (recency, per your own comment at line 861), an eval agent risks (a) overwriting its structured report (schema/summary/metrics/checks) with a bare {"status": "success"}, silently destroying the deliverable the downstream verifier depends on while the flow still reports SUCCEEDED, or (b) printing a sentinel the preset explicitly forbids. The status logic is correct (pass/fail map to success confirmation), but the instruction never tells the eval agent that its own pass/fail verdict already satisfies the contract, so it has no reason not to "helpfully" rewrite the file.

Recommendation:
Make the injected instruction vocabulary-aware. Either exempt Observe/Eval flows from the {"status": "success"} example (their preset already mandates a result.json), or reword the result.json channel so it accepts the flow's own status vocabulary — e.g. "write /workspace/result.json with a completion status (the eval preset's pass/fail verdicts qualify)" — so eval agents don't print a forbidden sentinel or clobber their report.

Suggested change
or write /workspace/result.json containing {{"status": "success"}}. Without one of these confirmations the run is marked FAILED.
or write /workspace/result.json with a completion status (the eval preset's
"pass"/"fail" verdicts qualify). Without a confirmation the run is marked FAILED.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 1ca8f90. Prompt resolution now selects an eval-specific final instruction when it sees preloop.eval.result/v1 or the existing no-sentinel directive. That instruction treats the structured report as the confirmation channel, preserves richer report fields, and explains pass/fail/error without offering the sentinel or a bare success object. Normal flows retain both confirmation channels.

Keep the final completion instruction from overriding Observe/Eval vocabulary or encouraging agents to clobber structured reports.

Co-authored-by: Cursor <cursoragent@cursor.com>
@d-mo

d-mo commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Fixed the new MEDIUM completion-instruction review in 1ca8f903.

  • Observe/Eval prompts now receive a contract-aware final instruction that preserves the structured preloop.eval.result/v1 report and does not offer the sentinel or a bare success object.
  • Normal prompts still receive the sentinel plus result artifact confirmation channels, with pass and fail accepted as completion statuses.
  • Added prompt-resolution coverage for both eval detection signatures and normal-flow behavior.

Tested with:
PYTHONPATH=/tmp/wt-pr234/backend /Users/dimo/git/spacecode/preloop-ee/.venv/bin/python -m pytest -q /tmp/wt-pr234/backend/tests/test_flow_orchestrator.py -k 'prompt_resolution_normal_uses_sentinel_instruction or prompt_resolution_eval_uses_result_artifact_instruction'

Result: 3 passed, 55 deselected.

@preloop-staging preloop-staging Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Preloop approves this PR. All previously raised issues have been addressed. Great work! See the summary comment below for the full review history.

@d-mo
d-mo merged commit 6b099f6 into main Aug 17, 2026
13 of 15 checks passed
@d-mo
d-mo deleted the fix/success-confirmation-channels branch August 17, 2026 17:43
d-mo added a commit that referenced this pull request Aug 17, 2026
The success-confirmation tests from #234 still patched
flow_orchestrator.create_agent_executor, which this branch replaced
with create_executor_for_execution. Document runner_pool on the flow
schema and CI trigger guide, and drop the leftover CHANGELOG line
from the merge.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant