Declarative Action Postconditions

After an action an agent (or a replay harness) usually has a concrete expectation: “a dialog saying ‘Saved’ should appear AND the Submit button should disable”. expect_poll / assert_eventually poll a single condition but have no notion of an action-bound postcondition spec, and they don’t diff against a before baseline (so they cannot express “a NEW dialog appeared” — only “a dialog exists”). trajectory_eval rubrics are whole-trajectory, not per-step screen state. postcondition fills the gap: a small JSON spec of clauses evaluated against the after-observation (optionally diffed against the before-observation), returning a per-clause pass/fail report.

Clauses: appears / disappears (diffed against before), enabled / disabled, text_present / text_absent, and count (equals / min). Pure-stdlib over element dicts; the spec is plain JSON so it rides into action files / MCP / the scheduler. Imports no PySide6.

Headless API

from je_auto_control import check_postcondition, compile_postcondition

spec = {"appears": {"role": "dialog", "name": "Saved"},
        "disabled": {"name": "Submit"}}
report = check_postcondition(after_elements, spec, before=before_elements)
if not report.ok:
    print("failed clauses:", report.failed)

# turn a spec into a predicate to drive expect_poll
predicate = compile_postcondition({"text_present": "Saved"})

check_postcondition returns a PostconditionReport (ok / clauses[{type, ok, detail}] — / failed). appears succeeds only when the element is in after and not in before (a genuinely new element); disappears requires a before frame. compile_postcondition returns an after -> bool predicate for pairing with expect_poll / assert_eventually.

Executor command

AC_check_postcondition (after / spec / before{ok, clauses, failed}) is exposed as the MCP tool ac_check_postcondition (read-only) and as the Script Builder command Check Postcondition under Native UI.