Repository navigation
feat(flow): induce shadow-mined episodes into candidate drafts - #1212
Merged
Merged
Conversation
Add compiler/motif_induction.py. It validates an openadapt.shadow.episodes/v1 export from openadapt-capture's shadow miner (strict schema: digests only, no unknown fields), maps each episode to a linear Workflow, and runs the existing induce_program over them. The result is a MotifCandidate whose status is always candidate and whose admitted and runnable fields are pinned to False. Anchors are placeholders, so record-next always starts with recording the task once. Write-shaped clicks are irreversible with risk_review_required. Checks the episodes suggest (file written, record written, row added, page reached) are proposals and never become effects. write_candidate writes candidate.json and draft-workflow.json, never workflow.json. No model calls and no network access; tests cover both. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
Capture's shadow export moves to openadapt.shadow.episodes/v2: each keyed digest becomes a reference local to the export (label-a, value-c), so a draft never carries a digest that could be checked against guesses for a short identifier after the fact. induce_candidate now reads v2 only. It refuses v1 with a clear message, refuses label_digest and value_digest fields, and refuses a token with an 8+ character hex part. A scrubbed chrome label (<dynamic_label>) is treated as no label. The fixtures are regenerated with Capture's v2 exporter. New tests check that no induced candidate carries a hex run other than its own export id. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…mat check Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
….json Regenerated with check_release_consistency.py --write-public-artifact-inventory on a clean export. Three entries, all under tests/fixtures/shadow. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…epted checks `openadapt-flow admit <recordings...>` (or `--candidate ID`) compiles each recording, induces one program from two or more, proposes the qualification checks, and stops once with a short summary. On accept it applies the checks through accept_proposal, which re-runs the oracle gate, and reseals the bundle. `admit decide DIR` decides later at a terminal. The decision record names who prepared and who decided (kind, id, route). An agent can't decide an admission it prepared, the regulated pack needs a person, and a decision binds the exact summary digest and bundle digest. Induction refusals, HALTed proposals and missing recordings stop with nothing to accept. Shadow-suggested checks are shown but never applied, and no admission is signed. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…MCP tools `openadapt-agent serve --admissions DIR` exposes the admit command over MCP. Recordings and candidates are named by folder under DIR, never by path. Without --delegate-approvals, decide_admission asks a person in the client's form (person:mcp-form). With it, the agent decides as the delegated approver (agent:ID). Either way the self-approval, regulated-pack and digest-binding rules in openadapt_flow.admit_pipeline apply. Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…legated agent Signed-off-by: Richard Abrich <richard.abrich@mldsai.com>
…oval' into flow/shadow-motif-mining-admit
An accepted admission now admits the workflow to the problems_only approval mode: its runs stop only for problems (a failed check, a changed screen, a step outside what was qualified) instead of before every save. openadapt-flow run reads this from the bundle's admission-decision.json and prints who admitted it. A flag or runtime.approval_mode still wins. The admitter is a person, or a delegated agent acting under an approver grant a registered person signed (#1234's openadapt-flow/approver-grant/v1), replacing the unsigned delegate decision. The grant must verify, name the agent, cover the workflow and let an agent approve consequential saves. The decision records the grant's digest, its signer and the signed grant itself. The mode applies only to the exact admitted bundle, and an agent's admission only while its grant still verifies. problems_only keeps its own gates, so every save still waits for a person until the switch is earned. MCP: openadapt-agent serve --admissions DIR --delegate-approvals ID --approver-grant FILE.
…oval' into flow/shadow-motif-mining-admit
…roval stays the strictest setting
# Conflicts: # packages/flow/openadapt_flow/__main__.py
Review fixes for the admission pipeline before merge: - Sign admission-pending.json and admission-decision.json with the local admission key (the JIT key directory), as JIT admissions on main are. admitted_approval_mode refuses an unsigned or edited decision record (admission_signature_invalid), so write access to a bundle can no longer set problems_only, and an edited pending file can't change who prepared an admission. - An agent can't decide an admission an unidentified MCP client (agent:mcp-client) prepared; that may be the same agent. A person can. - The summaries the MCP tools return are value-free: record-next codes, not induction's record-next lines, which can quote recorded labels, and no halt reason or shadow-suggestion text. - Test that an admitted bundle's problems_only still sends each save to a person until the signed switch is on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y runs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Review before merge (main merged in; Fail-open paths found and fixed in 7a9aeb6, each with a test that fails without the fix:
Checked and left as is:
Docs: new Follow-ups, not done here:
|
# Conflicts: # packages/agent/README.md
This was referenced Oct 19, 2026
abrichr
added a commit
that referenced
this pull request
Oct 19, 2026
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Ports the Flow part of Play 5 (openadapt-flow
feat/shadow-motif-mining) intopackages/flow.What it adds:
openadapt_flow.compiler.motif_inductionreads a shadow-episodes export (v2) from openadapt-capture's on-device motif mining. It turns repeated work into a candidate draft workflow. The code is deterministic, makes no model calls and uses no network. Digests stay out of drafts. A person must admit a candidate before it runs. Seedocs/SHADOW_CANDIDATES.md.Includes three shadow-episode fixtures, listed in
public-artifacts.json, and 26 tests intests/test_motif_induction.py.Related: capture side merged in openadapt-capture#156 (released in 1.3.0). Desktop side is openadapt-desktop#205.
Invariants: mining only proposes a draft. Nothing is admitted or authorized automatically, and replay of an admitted program still makes no model calls.
Local: ruff, ruff format, release-consistency pass;
test_motif_induction.py26 passed. Full suite on CI.One admission command (added after review)
openadapt-flow admit <recordings...>oradmit --candidate IDcompiles, induces and proposes checks. It then stops once with a short summary.acceptapplies the checks throughaccept_proposal, which re-runs the oracle gate.admit decide DIRdecides later at a terminal.openadapt-agent serve --admissions DIRaddsprepare_admission,list_admissionsanddecide_admission. A person decides in the client's form. With--delegate-approvals ID --approver-grant FILE, the agent decides as that delegated approver, under a grant a person signed (see below).admission-decision.jsonnames who prepared and who decided. An agent can't decide what it prepared, andregulatedneeds a person. A decision binds the summary digest and the bundle digest. Shadow suggestions are never applied, and nothing is signed.tests/test_admit_pipeline.py(15) andpackages/agent/tests/test_admission_tools.py(9). Both pass locally with the qualification-proposal, CLI-hints, motif and agent contract/CLI/README suites.Admission sets "only problems" under a person-signed grant (added)
problems_onlyapproval mode. Its runs stop only for problems: a failed check, a changed screen, or a step outside what was qualified. They no longer stop before every save.openadapt-flow runreads this from the bundle'sadmission-decision.jsonand prints who admitted it. A--approval-modeflag orruntime.approval_modestill wins.openadapt-flow/approver-grant/v1from Flow: person-signed approver grants, no self-approval, one-tap phone approval on the decision portal #1234). This replaces the unsigned delegate decision from feat(approval): delegated approvers, read-back field checks, triage and eligibility (carry-over of openadapt-flow#534) #1190. The grant must verify against the signer registry, name the agent, cover the workflow, and let an agent approve consequential saves. Otherwise the agent's decision is refused. The decision record keeps the grant's digest (policy_ref), its signer (on_behalf_of) and the signed grant itself.problems_onlykeeps its own gates, so every save still waits for a person until the switch is earned.tests/test_admit_pipeline.py(25) andpackages/agent/tests/test_admission_tools.py(9).This branch now includes
mainand #1234's branch (merged, not rebased). Once #1234 lands, its commits drop out of this diff.