Skip to content

Reusable subroutines: typed unit, fail-closed admission, re-qualification (part of #1892) - #1987

Open
abrichr wants to merge 1 commit into
mainfrom
agent/1892
Open

abrichr wants to merge 1 commit into
mainfrom
agent/1892

Conversation

@abrichr

@abrichr abrichr commented Nov 5, 2026

Copy link
Copy Markdown
Member

What kind of change does this PR introduce?

New engine feature, first slice. Part of #1892. It does not close the issue.

Summary

Workflows repeat the same steps, such as searching for a record or picking a date. This PR adds the reusable unit for those steps and the gate that decides whether a workflow may use it. It touches no existing file.

New module: packages/flow/openadapt_flow/subroutines.py.

  • The unit. A Subroutine is a typed signature plus a ProgramGraph body. The signature lists the inputs and their types, the surface kind, the entry checks and optional exit checks. At least one entry check must read the live screen. A parameter comparison, an absence check, OR or NOT does not count. A subroutine's identity is the SHA-256 of its signature and body, so any change makes a new subroutine.
  • What a body may hold. In this slice a body is narrow on purpose. It may contain only reversible, single-surface steps that read only their declared inputs. It refuses writes (effects, API or submit bindings, dialog answers, irreversible or unreviewed risk), reads, secrets, surface changes, nested calls, loops, business decisions and plaintext identity bands. Every non-terminal state must have an outgoing edge, so a run can't leave the body without passing the exit checks.
  • Run-time boundary. embed() puts the subroutine into Workflow.subflows under subroutine/<name>/<sha256>. It wraps the body in branches: the run halts if an entry check fails, and halts again if an exit check fails before returning. The existing interpreter runs these branches as they are, so the replayer is unchanged.
  • Library and re-qualification. SubroutineLibrary records each name's versions, which digests are qualified, and which workflows use which digest. When a new version is published, it returns every registered workflow still pinned to an older one. Those workflows are refused until they embed the new version and register again. Loading a library from JSON recomputes every digest, so an edited document is refused.
  • Admission. admit_workflow() refuses each use with a stable code: unknown, superseded, not qualified, not registered, embedded body edited (including a bypassed entry check), surface not declared or wrong, multi-surface, or an input missing, mistyped, wider enum, or secret. Messages name subroutines, inputs and codes only, never screen text. A workflow with no subroutines passes this gate unchanged.
  • Detection. mine_recurring_sequences() proposes candidates that recur in at least 2 workflows. It compares steps only by action kind, whether a parameter feeds the step, key chord and accessibility role. It never compares labels, names, selectors, URLs or application names. Candidates are always admitted=False and runnable=False.

Design notes: packages/flow/docs/design/SUBROUTINES.md.

How it was tested

  • tests/test_subroutines.py: 28 tests. They fail on base, because the module does not exist there. They cover:
    • entry-check strength
    • digest identity
    • each body refusal
    • live runs on the real Replayer with fake backend and vision: the body runs only when the entry check holds, and the run halts with no actions when it fails. A failing exit check halts after the body ran.
    • validate_workflow accepts an embedded workflow
    • library versions, no rollback, JSON round-trip and tamper refusal
    • every admission code, including re-qualification of 2 workflows after a change
    • detection matches by shape only and never leaks labels
  • Next to that, 109 passed across the subroutine, program IR phase 1 and 2, and motif induction suites.
  • These pass locally: Ruff lint and format (1347 files), full mypy (594 sources), engine generality, release consistency, source boundary, and the Python 3.10 syntax check.

Not tested / not in this slice

  • No lab or real application run. Everything is software-only, with synthetic data.
  • flow qualify and certification don't call register or admit_workflow yet. That wiring is the next slice.
  • Input renaming at the call site isn't supported. The caller must declare inputs with the same names.
  • Outputs, writes, secrets and multi-surface bodies are refused for now.
  • The library lives in memory and is saved as JSON. It isn't written to disk as a sealed set yet.
  • Boundary checks use today's Predicate. Once the IR: a small, safe expression language for data transforms between steps #1891 expression language lands, they should move to it.

Checklist

  • I have tested this change locally
  • Documentation is updated if needed
  • CI passes

)

A subroutine is a typed signature (inputs, surface kind, entry and exit
checks) plus a ProgramGraph body, identified by the SHA-256 of both.
Workflows embed it under subroutine/<name>/<sha256>, wrapped so the
existing interpreter halts when an entry or exit check does not hold.

The library keeps versions, qualifications and registered users.
Publishing a new version lists every workflow pinned to an older one,
and admission refuses those uses until they are re-embedded and
registered again. Admission also refuses unknown, unqualified or
unregistered digests, edited embedded bodies, surface and input
mismatches.

Detection proposes recurring sequences by step shape only, never by
labels or application names.
@abrichr
abrichr marked this pull request as ready for review November 5, 2026 18:19
@abrichr abrichr added the review-needed Needs an independent review from another vendor's agent label Nov 5, 2026
@abrichr

abrichr commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

Independent adversarial review: BLOCK

From: Codex

Reviewed head e752011b469fa23e56596f0ab456f8d414d1e8c0 against literal parent 9f503bd8115710bd181b5d0cfaf6ece144a08fad.

  1. P2 — Check nested anchors before accepting an identity band. packages/flow/openadapt_flow/subroutines.py:336 checks only step.anchor and step.drag_end_anchor. Anchors in entry/exit predicates, transition guards, step.guard and step.wait_until can still carry context_text or structured_identity. body_problems() traverses those predicates only for parameter references. Such a unit can therefore pass construction and publication with the plaintext identity band embedded in its reusable content and JSON export, contrary to the body's stated refusal contract. Apply the same identity-band check to every predicate anchor, including nested operands, and cover each location with a rejection test.

  2. P2 — Reject duplicate step IDs before constructing the graph. At packages/flow/openadapt_flow/subroutines.py:1073, assigning states[step.id] silently replaces any earlier step with that ID. For example, [TYPE(query, id="enter"), KEY(F, id="duplicate"), KEY(Enter, id="duplicate")] produces a graph containing the TYPE and Enter actions; F disappears, and the remaining graph passes the body checks. A constructor advertised as preserving consecutive steps must reject repeated IDs or generate unique state IDs while preserving every action and edge. Add a regression that checks the action sequence or rejects the duplicate input.

Independent WSL checks: the head passed 95 tests across test_subroutines.py, test_program_ir_phase1.py, test_program_ir_phase2.py and test_motif_induction.py; focused Ruff passed. The literal parent passed the 67 existing tests in the latter three files. Additional adversarial regressions were prepared but not executed; both findings above are based on the exact source. No real application, model, benchmark or native desktop run was performed. Admission/certification wiring and sealed persistence remain outside this first slice.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review-needed Needs an independent review from another vendor's agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant