openadapt-agent 2.1.0__tar.gz → 2.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/ci.yml +24 -2
  2. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/codeql.yml +2 -2
  3. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/release.yml +4 -4
  4. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/PKG-INFO +22 -8
  5. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/README.md +17 -5
  6. openadapt_agent-2.2.0/docs/REPAIR.md +95 -0
  7. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/manifest.json +1 -1
  8. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/pyproject.toml +8 -3
  9. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/server.json +2 -2
  10. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/__init__.py +1 -1
  11. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/bridge.py +90 -1
  12. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/cli.py +144 -0
  13. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/contract.py +29 -0
  14. openadapt_agent-2.2.0/src/openadapt_agent/repair.py +724 -0
  15. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/runner.py +81 -9
  16. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/service.py +58 -2
  17. openadapt_agent-2.2.0/tests/test_repair.py +398 -0
  18. openadapt_agent-2.2.0/tests/test_repair_e2e.py +201 -0
  19. openadapt_agent-2.2.0/tests/test_repair_hold.py +107 -0
  20. openadapt_agent-2.2.0/uv.lock +2643 -0
  21. openadapt_agent-2.1.0/uv.lock +0 -1300
  22. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/CODEOWNERS +0 -0
  23. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/dependabot.yml +0 -0
  24. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/dependency-review.yml +0 -0
  25. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/secret-scan.yml +0 -0
  26. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.gitignore +0 -0
  27. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.gitleaks.toml +0 -0
  28. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.mcpbignore +0 -0
  29. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/LICENSE +0 -0
  30. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/SECURITY.md +0 -0
  31. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/DESIGN.md +0 -0
  32. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/DISTRIBUTION.md +0 -0
  33. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/MAILBOX_CLI.md +0 -0
  34. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/examples/sandbox-results.json +0 -0
  35. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/llms.txt +0 -0
  36. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/capture_sandbox_examples.py +0 -0
  37. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_dist.py +0 -0
  38. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_mcpb.py +0 -0
  39. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_release_artifacts.py +0 -0
  40. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_source_boundary.py +0 -0
  41. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/smoke_client.py +0 -0
  42. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/skills/openadapt-gui-write/SKILL.md +0 -0
  43. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/source-policy.public.json +0 -0
  44. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/__main__.py +0 -0
  45. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/approval.py +0 -0
  46. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/attended.py +0 -0
  47. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/authoring.py +0 -0
  48. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/bundles.py +0 -0
  49. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/cards.py +0 -0
  50. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/copy.py +0 -0
  51. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/flow_service.py +0 -0
  52. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/mailbox.py +0 -0
  53. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/mcp.py +0 -0
  54. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/mcpb_entry.py +0 -0
  55. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/runner_bind.py +0 -0
  56. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/runs.py +0 -0
  57. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/sandbox.py +0 -0
  58. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/seal.py +0 -0
  59. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/seal_cli.py +0 -0
  60. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/skill.py +0 -0
  61. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/tutorial.py +0 -0
  62. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/conftest.py +0 -0
  63. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/fixtures/cloud-ts-signed-seal.json +0 -0
  64. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_approval.py +0 -0
  65. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_approval_flow.py +0 -0
  66. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_attended_bridge.py +0 -0
  67. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_authoring.py +0 -0
  68. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_bridge.py +0 -0
  69. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_cards.py +0 -0
  70. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_cli.py +0 -0
  71. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_contract.py +0 -0
  72. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_distribution.py +0 -0
  73. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_mailbox.py +0 -0
  74. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_mcp_server.py +0 -0
  75. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_mcpb_entry.py +0 -0
  76. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_partner_kit.py +0 -0
  77. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_public_lifecycle.py +0 -0
  78. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_readme_commands.py +0 -0
  79. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_runner.py +0 -0
  80. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_runs.py +0 -0
  81. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_sandbox.py +0 -0
  82. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_sandbox_flow.py +0 -0
  83. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_seal.py +0 -0
  84. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_seal_cli.py +0 -0
  85. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_seal_e2e.py +0 -0
  86. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_service.py +0 -0
  87. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_skill.py +0 -0
  88. {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_tutorial.py +0 -0
@@ -25,7 +25,7 @@ jobs:
25
25
  with:
26
26
  python-version: ${{ matrix.python-version }}
27
27
  cache: pip
28
- - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
28
+ - uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
29
29
  if: matrix.dependency-set == 'floor'
30
30
  - name: Verify lock consistency
31
31
  if: matrix.dependency-set == 'floor'
@@ -34,7 +34,7 @@ jobs:
34
34
  if: matrix.dependency-set == 'floor'
35
35
  run: >-
36
36
  pip install -e ".[dev]"
37
- "openadapt-flow==2.0.0"
37
+ "openadapt-flow==2.1.0"
38
38
  "mcp==1.28.0"
39
39
  - name: Install MCP 2 floor
40
40
  if: matrix.dependency-set == 'mcp2-floor'
@@ -78,3 +78,25 @@ jobs:
78
78
  DO_NOT_TRACK: "1"
79
79
  OPENADAPT_TELEMETRY: "0"
80
80
  run: pytest -q tests/test_sandbox_flow.py tests/test_approval_flow.py
81
+
82
+ repair-e2e:
83
+ # halt -> needs_repair -> teach_repair -> approval -> replay, on Flow's
84
+ # rendered dialog fixture with its real replayer, OCR, and campaigns.
85
+ runs-on: ubuntu-latest
86
+ steps:
87
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
88
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
89
+ with:
90
+ python-version: "3.12"
91
+ cache: pip
92
+ - name: Install with the released Flow (ships openadapt_flow.repair.dialog_fixture)
93
+ run: |
94
+ pip install -e ".[dev]" "openadapt-flow>=2.1.0,<3"
95
+ pip install opencv-python-headless rapidocr-onnxruntime
96
+ - name: Run the repair end-to-end test
97
+ env:
98
+ DO_NOT_TRACK: "1"
99
+ OPENADAPT_TELEMETRY: "0"
100
+ run: |
101
+ python -c "import openadapt_flow.repair.dialog_fixture"
102
+ pytest -q -rs tests/test_repair_e2e.py tests/test_repair_hold.py
@@ -28,10 +28,10 @@ jobs:
28
28
  - name: Checkout
29
29
  uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30
30
  - name: Initialize CodeQL
31
- uses: github/codeql-action/init@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
31
+ uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
32
32
  with:
33
33
  languages: ${{ matrix.language }}
34
34
  - name: Perform CodeQL Analysis
35
- uses: github/codeql-action/analyze@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0
35
+ uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
36
36
  with:
37
37
  category: "/language:${{ matrix.language }}"
@@ -48,7 +48,7 @@ jobs:
48
48
  - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
49
49
  with:
50
50
  python-version: "3.12"
51
- - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
51
+ - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
52
52
  with:
53
53
  node-version: "20"
54
54
  - name: Install tooling
@@ -94,13 +94,13 @@ jobs:
94
94
  sys.exit(1 if errors else 0)
95
95
  PY
96
96
  - name: Upload built distributions
97
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
97
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
98
98
  with:
99
99
  name: dist
100
100
  path: dist/
101
101
  if-no-files-found: error
102
102
  - name: Upload local MCPB
103
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
103
+ uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
104
104
  with:
105
105
  name: mcpb
106
106
  path: mcpb-dist/
@@ -137,7 +137,7 @@ jobs:
137
137
  exit 1
138
138
  fi
139
139
  - name: Download built distributions
140
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
140
+ uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
141
141
  with:
142
142
  name: dist
143
143
  path: dist/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: openadapt-agent
3
- Version: 2.1.0
3
+ Version: 2.2.0
4
4
  Summary: Let your AI agent enter information in apps with no API and get back whether it saved (MCP and Agent Skills)
5
5
  Project-URL: Homepage, https://github.com/OpenAdaptAI/openadapt-agent
6
6
  Project-URL: Repository, https://github.com/OpenAdaptAI/openadapt-agent
@@ -24,13 +24,15 @@ Requires-Dist: anyio>=4.0
24
24
  Requires-Dist: cryptography>=50.0.0
25
25
  Requires-Dist: jsonschema>=4.20.0
26
26
  Requires-Dist: mcp<3,>=1.28
27
- Requires-Dist: openadapt-flow<3,>=2.0.0
27
+ Requires-Dist: openadapt-flow<3,>=2.1.0
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: build>=1.2.0; extra == 'dev'
30
30
  Requires-Dist: pytest>=8.0.0; extra == 'dev'
31
31
  Requires-Dist: ruff>=0.4.0; extra == 'dev'
32
+ Provides-Extra: repair-screens
33
+ Requires-Dist: openadapt-privacy[presidio]>=1.1.2; extra == 'repair-screens'
32
34
  Provides-Extra: tutorial
33
- Requires-Dist: openadapt-flow[browser,macos]<3,>=2.0.0; extra == 'tutorial'
35
+ Requires-Dist: openadapt-flow[browser,macos]<3,>=2.1.0; extra == 'tutorial'
34
36
  Description-Content-Type: text/markdown
35
37
 
36
38
  # OpenAdapt Agent
@@ -42,13 +44,24 @@ Your AI agent decides what to enter. OpenAdapt enters it in the app and checks t
42
44
 
43
45
  Use it if your agent reads referrals, intake forms, or authorizations and works out what belongs in the record, but a person still types the result into an app with no usable API. Examples are an electronic medical record (EMR) and a payer portal. This package is the tool your agent calls to do that typing.
44
46
 
45
- - A person records the task once in the real app. OpenAdapt replays those steps, and by default a run makes no AI model calls.
47
+ - A person or your agent shows OpenAdapt the task in the real app. OpenAdapt replays those steps, and by default a run makes no AI model calls.
46
48
  - Before it types, OpenAdapt checks that the right record is open. After it saves, it reads the record back.
47
49
  - Your agent gets one of four outcomes: done and checked, stopped before saving, check the record, or didn't run. When OpenAdapt can't confirm a save, it says so, and a person checks the record.
48
50
  - It runs on your customer's computer, one that can already open the app. Your agent, or a small program acting for it, has to run on that computer too, since it starts OpenAdapt and talks to it directly. If your agent runs in your own cloud, ask us about a hosted route.
49
51
 
50
52
  Pick one task your agent hands to staff today and [talk to us about connecting it](https://openadapt.ai/qualify?why=embed). Engineers can [try the sandbox](#try-it-in-three-steps) first.
51
53
 
54
+ ## Give your agent a program, not a mouse
55
+
56
+ Your agent doesn't send screenshots to a model and click. It calls a taught workflow as one typed tool, OpenAdapt runs the same steps every time, and by default your agent gets back an outcome and counts, not screens or recorded values.
57
+
58
+ ```bash
59
+ openadapt-agent serve --mode production --bundles <bundle or directory of bundles>
60
+ openadapt-agent emit-skill <bundle> --out ~/.claude/skills
61
+ ```
62
+
63
+ [Try it in three steps](#try-it-in-three-steps) runs a sandbox with no account. [Serve your own workflows](#serve-your-own-workflows) and [Write an Agent Skill](#write-an-agent-skill) cover both commands.
64
+
52
65
  ## What changes for your customer's team
53
66
 
54
67
  | Step | Today | With OpenAdapt |
@@ -82,7 +95,7 @@ flowchart LR
82
95
  | What your product does with each outcome | One result per request, and no second entry when your agent repeats a `request_id` |
83
96
  | Your tests against the four outcomes | A sandbox that returns each outcome on demand |
84
97
 
85
- Your customer provides the computer and a person for runs that stop. Someone who does the task today records it once. Your agent can guide that recording (see [Record a first workflow](#record-a-first-workflow)), or [talk to us](https://openadapt.ai/qualify?why=embed) about setting it up.
98
+ Your customer provides the computer and a person for runs that stop. Someone who does the task today records it, or your agent drives the recording. A person accepts the result before it runs, and on native Windows apps, Citrix and remote desktop a person has to do the recording. Your agent can guide that recording (see [Record a first workflow](#record-a-first-workflow)), or [talk to us](https://openadapt.ai/qualify?why=embed) about setting it up.
86
99
 
87
100
  ## Try it in three steps
88
101
 
@@ -123,7 +136,7 @@ The result looks like this (some fields left out):
123
136
 
124
137
  This install simulates each run without opening MockMed, and every result says so. To watch the real engine do the work, see [Drive MockMed with the real engine](#drive-mockmed-with-the-real-engine).
125
138
 
126
- PyPI has version 2.0.1, which came out before the sandbox. After 2.1.0 is on PyPI, step 1 can be `claude mcp add openadapt -- uvx --python 3.12 openadapt-agent serve`.
139
+ Version 2.1.0 on PyPI includes the sandbox, so step 1 can also be `claude mcp add openadapt -- uvx --python 3.12 openadapt-agent serve`.
127
140
 
128
141
  ### See every outcome
129
142
 
@@ -150,7 +163,7 @@ claude mcp add openadapt -- uvx --python 3.12 --from 'openadapt-agent[tutorial]
150
163
 
151
164
  When the server starts, Flow records the workflow once and, if needed, downloads a browser (about 300 MB). The first request waits for that. In our test on a Mac, the first request took about 80 seconds. The call returned `running` after 60 seconds, and the agent called `get_run` for the result. If setup takes more than 15 minutes, the request ends `did_not_run`. With Flow, a `done` result has `proof: "local"` instead of `"simulated"`.
152
165
 
153
- Until 2.1.0 is on PyPI, use the command above. The simulated sandbox suggests the same command in its `list_workflows` result. A command that installs `openadapt-agent[tutorial]` from PyPI gets 2.0.1, which has no sandbox: the server exits with an error that `--bundles` is required.
166
+ The simulated sandbox suggests the same command in its `list_workflows` result.
154
167
 
155
168
  ## What your agent gets back
156
169
 
@@ -186,6 +199,7 @@ The check that stops a second entry covers one runs folder. See `--runs-dir` und
186
199
  - `proof`: `local` for a `done` that was checked on your customer's computer, `simulated` for a sandbox run that didn't open the app, and `none` for every other outcome.
187
200
  - `model_calls`: on runs that reached Flow, how many AI model calls the run made. It's 0 unless model grounding, a fallback that asks an AI model where to click, is turned on (see [Security](#security)).
188
201
  - `record_changed`: `yes`, `no`, or `unknown`. It's `no` only when OpenAdapt can show that nothing was written. `request_id_conflict` gives `unknown`, because the earlier request may have saved.
202
+ - `needs_repair`: on `needs_review` or `did_not_run` results where the run stopped at a step. It gives the step id, the reason, and a one-time `repair_token`, and never a screen. If a person turned on screen access for repairs, your agent can call `get_repair_screen` and `teach_repair` to propose a fix, which a person must approve. See [docs/REPAIR.md](docs/REPAIR.md).
189
203
 
190
204
  ## Three tools
191
205
 
@@ -431,7 +445,7 @@ openadapt-agent seal verify https://example.com/receipt.json --json
431
445
 
432
446
  ## Release status
433
447
 
434
- The [signed production record](https://openadapt.ai/production-lifecycle.json) says whether a release of this package is admitted for production use. An admission lasts at most 30 days and can be revoked, and a release without a valid one is "not actively admitted." On 2026-10-14, no release of this package was admitted. The admission for 2.0.1 was issued on 2026-09-02 and is past the 30-day limit, and the version on GitHub (2.1.0) has none. Check the record before you use a release with real patient data.
448
+ Check the [signed production record](https://openadapt.ai/production-lifecycle.json) before you use a release with real patient data. An admission lasts at most 30 days and can be revoked.
435
449
 
436
450
  Under Flow's `standard` and `regulated` profiles, each workflow version also needs its own admission (see [Get a workflow ready to run](#get-a-workflow-ready-to-run)). On 2026-10-14, every entry in the [public list of workflow admissions](https://openadapt.ai/production-workflow-admissions.json) came from synthetic tests (`remote-safe-synthetic`).
437
451
 
@@ -7,13 +7,24 @@ Your AI agent decides what to enter. OpenAdapt enters it in the app and checks t
7
7
 
8
8
  Use it if your agent reads referrals, intake forms, or authorizations and works out what belongs in the record, but a person still types the result into an app with no usable API. Examples are an electronic medical record (EMR) and a payer portal. This package is the tool your agent calls to do that typing.
9
9
 
10
- - A person records the task once in the real app. OpenAdapt replays those steps, and by default a run makes no AI model calls.
10
+ - A person or your agent shows OpenAdapt the task in the real app. OpenAdapt replays those steps, and by default a run makes no AI model calls.
11
11
  - Before it types, OpenAdapt checks that the right record is open. After it saves, it reads the record back.
12
12
  - Your agent gets one of four outcomes: done and checked, stopped before saving, check the record, or didn't run. When OpenAdapt can't confirm a save, it says so, and a person checks the record.
13
13
  - It runs on your customer's computer, one that can already open the app. Your agent, or a small program acting for it, has to run on that computer too, since it starts OpenAdapt and talks to it directly. If your agent runs in your own cloud, ask us about a hosted route.
14
14
 
15
15
  Pick one task your agent hands to staff today and [talk to us about connecting it](https://openadapt.ai/qualify?why=embed). Engineers can [try the sandbox](#try-it-in-three-steps) first.
16
16
 
17
+ ## Give your agent a program, not a mouse
18
+
19
+ Your agent doesn't send screenshots to a model and click. It calls a taught workflow as one typed tool, OpenAdapt runs the same steps every time, and by default your agent gets back an outcome and counts, not screens or recorded values.
20
+
21
+ ```bash
22
+ openadapt-agent serve --mode production --bundles <bundle or directory of bundles>
23
+ openadapt-agent emit-skill <bundle> --out ~/.claude/skills
24
+ ```
25
+
26
+ [Try it in three steps](#try-it-in-three-steps) runs a sandbox with no account. [Serve your own workflows](#serve-your-own-workflows) and [Write an Agent Skill](#write-an-agent-skill) cover both commands.
27
+
17
28
  ## What changes for your customer's team
18
29
 
19
30
  | Step | Today | With OpenAdapt |
@@ -47,7 +58,7 @@ flowchart LR
47
58
  | What your product does with each outcome | One result per request, and no second entry when your agent repeats a `request_id` |
48
59
  | Your tests against the four outcomes | A sandbox that returns each outcome on demand |
49
60
 
50
- Your customer provides the computer and a person for runs that stop. Someone who does the task today records it once. Your agent can guide that recording (see [Record a first workflow](#record-a-first-workflow)), or [talk to us](https://openadapt.ai/qualify?why=embed) about setting it up.
61
+ Your customer provides the computer and a person for runs that stop. Someone who does the task today records it, or your agent drives the recording. A person accepts the result before it runs, and on native Windows apps, Citrix and remote desktop a person has to do the recording. Your agent can guide that recording (see [Record a first workflow](#record-a-first-workflow)), or [talk to us](https://openadapt.ai/qualify?why=embed) about setting it up.
51
62
 
52
63
  ## Try it in three steps
53
64
 
@@ -88,7 +99,7 @@ The result looks like this (some fields left out):
88
99
 
89
100
  This install simulates each run without opening MockMed, and every result says so. To watch the real engine do the work, see [Drive MockMed with the real engine](#drive-mockmed-with-the-real-engine).
90
101
 
91
- PyPI has version 2.0.1, which came out before the sandbox. After 2.1.0 is on PyPI, step 1 can be `claude mcp add openadapt -- uvx --python 3.12 openadapt-agent serve`.
102
+ Version 2.1.0 on PyPI includes the sandbox, so step 1 can also be `claude mcp add openadapt -- uvx --python 3.12 openadapt-agent serve`.
92
103
 
93
104
  ### See every outcome
94
105
 
@@ -115,7 +126,7 @@ claude mcp add openadapt -- uvx --python 3.12 --from 'openadapt-agent[tutorial]
115
126
 
116
127
  When the server starts, Flow records the workflow once and, if needed, downloads a browser (about 300 MB). The first request waits for that. In our test on a Mac, the first request took about 80 seconds. The call returned `running` after 60 seconds, and the agent called `get_run` for the result. If setup takes more than 15 minutes, the request ends `did_not_run`. With Flow, a `done` result has `proof: "local"` instead of `"simulated"`.
117
128
 
118
- Until 2.1.0 is on PyPI, use the command above. The simulated sandbox suggests the same command in its `list_workflows` result. A command that installs `openadapt-agent[tutorial]` from PyPI gets 2.0.1, which has no sandbox: the server exits with an error that `--bundles` is required.
129
+ The simulated sandbox suggests the same command in its `list_workflows` result.
119
130
 
120
131
  ## What your agent gets back
121
132
 
@@ -151,6 +162,7 @@ The check that stops a second entry covers one runs folder. See `--runs-dir` und
151
162
  - `proof`: `local` for a `done` that was checked on your customer's computer, `simulated` for a sandbox run that didn't open the app, and `none` for every other outcome.
152
163
  - `model_calls`: on runs that reached Flow, how many AI model calls the run made. It's 0 unless model grounding, a fallback that asks an AI model where to click, is turned on (see [Security](#security)).
153
164
  - `record_changed`: `yes`, `no`, or `unknown`. It's `no` only when OpenAdapt can show that nothing was written. `request_id_conflict` gives `unknown`, because the earlier request may have saved.
165
+ - `needs_repair`: on `needs_review` or `did_not_run` results where the run stopped at a step. It gives the step id, the reason, and a one-time `repair_token`, and never a screen. If a person turned on screen access for repairs, your agent can call `get_repair_screen` and `teach_repair` to propose a fix, which a person must approve. See [docs/REPAIR.md](docs/REPAIR.md).
154
166
 
155
167
  ## Three tools
156
168
 
@@ -396,7 +408,7 @@ openadapt-agent seal verify https://example.com/receipt.json --json
396
408
 
397
409
  ## Release status
398
410
 
399
- The [signed production record](https://openadapt.ai/production-lifecycle.json) says whether a release of this package is admitted for production use. An admission lasts at most 30 days and can be revoked, and a release without a valid one is "not actively admitted." On 2026-10-14, no release of this package was admitted. The admission for 2.0.1 was issued on 2026-09-02 and is past the 30-day limit, and the version on GitHub (2.1.0) has none. Check the record before you use a release with real patient data.
411
+ Check the [signed production record](https://openadapt.ai/production-lifecycle.json) before you use a release with real patient data. An admission lasts at most 30 days and can be revoked.
400
412
 
401
413
  Under Flow's `standard` and `regulated` profiles, each workflow version also needs its own admission (see [Get a workflow ready to run](#get-a-workflow-ready-to-run)). On 2026-10-14, every entry in the [public list of workflow admissions](https://openadapt.ai/production-workflow-admissions.json) came from synthetic tests (`remote-safe-synthetic`).
402
414
 
@@ -0,0 +1,95 @@
1
+ # Help repair a halted run
2
+
3
+ When a workflow stops at a step it can't do, for example because a dialog
4
+ covers the button it clicks, `run_workflow` returns `needs_review` and adds a
5
+ `needs_repair` block. Your agent can then show OpenAdapt the one click that
6
+ gets past that step. OpenAdapt turns the click into a proposed repair, and a
7
+ person reviews, tests, and approves it before any run uses it. Until then the
8
+ workflow keeps stopping at that step.
9
+
10
+ Runs that don't stop never use any of this. They make no model calls and
11
+ cost nothing extra.
12
+
13
+ ## What `needs_repair` contains
14
+
15
+ ```json
16
+ "needs_repair": {
17
+ "halt_reason": "unexpected_screen",
18
+ "step_id": "s_verify",
19
+ "repair_token": "…",
20
+ "token_id": "2a7765621469",
21
+ "screen_access": "scrubbed_only",
22
+ "teach_repair_available": true,
23
+ "human_approval_required": false,
24
+ "next_action": "Call get_repair_screen to see the screen, then teach_repair …"
25
+ }
26
+ ```
27
+
28
+ It never contains a screen. It appears only on `needs_review` and
29
+ `did_not_run`, so a run that may have saved (`not_sure_if_saved`) never
30
+ invites a repair.
31
+
32
+ ## Screen access is off until a person turns it on
33
+
34
+ Only a person at the computer changes the setting, with the CLI. No MCP tool
35
+ reads or writes it, so nothing your agent says can turn it on.
36
+
37
+ ```bash
38
+ openadapt-agent repair access --workflow save_encounter \
39
+ --screens scrubbed_only --by "Dana Lee" --file repair-access.json
40
+ openadapt-agent serve --mode production --bundles ./bundles \
41
+ --repair-access repair-access.json
42
+ ```
43
+
44
+ Use `--org` instead of `--workflow` to set a default for every workflow. A
45
+ workflow setting overrides the default. An unreadable file, or a setting
46
+ without a person's name, counts as `off`.
47
+
48
+ | `--screens` | What the agent may do |
49
+ | --- | --- |
50
+ | `off` (default) | Nothing. `get_repair_screen` and `teach_repair` aren't listed. A person handles the halt. |
51
+ | `scrubbed_only` | See the screen after [openadapt-privacy](https://github.com/OpenAdaptAI/openadapt-privacy) removes names, dates of birth, record numbers, and other personal data, then teach one click. |
52
+ | `ask_each_time` | Same, but first a person runs `openadapt-agent repair approve-screen TOKEN_ID` for that one repair. The command saves the exact scrubbed image the agent would get and asks the person to type the token id. The agent then gets that image and no other. |
53
+ | `allow_unscrubbed` | See the screen without scrubbing. The CLI asks the person to type `share unscrubbed` at a terminal before it saves this setting. |
54
+
55
+ If scrubbing fails, or `openadapt-privacy` isn't installed, no screen is
56
+ shared. Install it with `pip install "openadapt-agent[repair-screens]"`, then the spaCy model openadapt-privacy loads: `python -m spacy download en_core_web_sm`. Image scrubbing also needs the Tesseract binary that Presidio's image redactor calls.
57
+
58
+ `approve-screen` and `repair access --screens allow_unscrubbed` refuse to run
59
+ without a terminal, so a script piping input can't approve. An agent that has
60
+ its own shell on the same computer can still open a terminal, so give
61
+ calling agents a separate account when this matters.
62
+
63
+ ## The repair tools
64
+
65
+ - `get_repair_screen(repair_token)` returns `screen_png_base64` and
66
+ `scrubbed`.
67
+ - `teach_repair(repair_token, demonstration)` takes
68
+ `{"action": "click", "x": 140, "y": 108}`. OpenAdapt takes a screenshot,
69
+ performs the click, and takes another, then hands that recording to
70
+ openadapt-flow's teach loop. It returns `status: pending_approval` and a
71
+ `candidate_id`, or `status: refused` when the teach loop can't use the
72
+ click. Each token works once, and it's used up before the click happens.
73
+
74
+ ## What a person does next
75
+
76
+ The proposed repair is an openadapt-flow repair candidate in
77
+ `RUNS_DIR/.repair/repair-store/<id>`. The person uses the usual
78
+ `openadapt-flow repair` commands with `--store` set to that folder: review,
79
+ replay and fault campaigns, approve, stage, and canary. The candidate records
80
+ which agent taught it, which run and step it fixes, the screen access mode,
81
+ how many screens the agent saw, whether they were scrubbed and by what, and
82
+ who approved sharing. Flow refuses review or approval from the agent's id.
83
+
84
+ Once the candidate reaches canary, `run_workflow` uses the repaired bundle.
85
+
86
+ ## The screen the agent sees
87
+
88
+ With `--repair-access`, `serve` runs Flow with `--hold-on-halt` (300 seconds by
89
+ default; change it with `--hold-on-halt SECONDS`, or 0 to turn it off). A
90
+ halted browser run then keeps its page open, and the server returns
91
+ `needs_repair` as soon as Flow reports the halt. `get_repair_screen` and
92
+ `teach_repair` act on that page through a private socket that only this server
93
+ knows the secret for. After `teach_repair`, or when the hold times out, Flow
94
+ closes the browser. If no held page is available, the server falls back to its
95
+ configured local screen.
@@ -2,7 +2,7 @@
2
2
  "manifest_version": "0.4",
3
3
  "name": "openadapt-agent",
4
4
  "display_name": "OpenAdapt",
5
- "version": "2.1.0",
5
+ "version": "2.2.0",
6
6
  "description": "Lets your AI agent enter information in an app with no API and get back whether it saved.",
7
7
  "long_description": "Your agent sends a workflow name, its inputs, and its own request id. OpenAdapt enters the values in the app's own screens on this computer, reads the saved record back, and returns done, needs review, not sure if saved, or did not run. The same request id never writes twice. Workflow files and evidence stay on this computer.",
8
8
  "author": {
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openadapt-agent"
7
- version = "2.1.0"
7
+ version = "2.2.0"
8
8
  description = "Let your AI agent enter information in apps with no API and get back whether it saved (MCP and Agent Skills)"
9
9
  readme = "README.md"
10
10
  # openadapt-flow pins <3.13 (rapidocr-onnxruntime); this package inherits it.
@@ -39,7 +39,7 @@ dependencies = [
39
39
  # 2.0 gates every saving run by default (each_item); the agent reports
40
40
  # that wait as waiting_for_approval (tests/test_approval_flow.py) and
41
41
  # 2.0 ships cloud_seal for Cloud-connected Seals.
42
- "openadapt-flow>=2.0.0,<3",
42
+ "openadapt-flow>=2.1.0,<3",
43
43
  # Flow uses cryptography for encrypted bundles and signed runtime evidence.
44
44
  # Keep the published Agent artifact above the PKCS#7 oracle fixed by 50.0.0
45
45
  # (GHSA-g6cj-pr64-35w5), even when an existing environment already has an
@@ -60,12 +60,17 @@ dev = [
60
60
  "pytest>=8.0.0",
61
61
  "ruff>=0.4.0",
62
62
  ]
63
+ repair-screens = [
64
+ # Scrubs screens before a calling agent sees them during a repair. Without
65
+ # it, scrubbed_only and ask_each_time share no screen.
66
+ "openadapt-privacy[presidio]>=1.1.2",
67
+ ]
63
68
  tutorial = [
64
69
  # The sandbox's real engine drives MockMed through Flow's tutorial entry
65
70
  # query, added in openadapt-flow 1.29.0. Older Flow falls back to the
66
71
  # simulated engine. Flow's macos extra (pyobjc, darwin-only markers) lets
67
72
  # `serve --authoring` without --url pin the frontmost macOS window.
68
- "openadapt-flow[browser,macos]>=2.0.0,<3",
73
+ "openadapt-flow[browser,macos]>=2.1.0,<3",
69
74
  ]
70
75
 
71
76
  [project.scripts]
@@ -8,13 +8,13 @@
8
8
  "url": "https://github.com/OpenAdaptAI/openadapt-agent",
9
9
  "source": "github"
10
10
  },
11
- "version": "2.1.0",
11
+ "version": "2.2.0",
12
12
  "packages": [
13
13
  {
14
14
  "registryType": "pypi",
15
15
  "registryBaseUrl": "https://pypi.org",
16
16
  "identifier": "openadapt-agent",
17
- "version": "2.1.0",
17
+ "version": "2.2.0",
18
18
  "runtimeHint": "uvx",
19
19
  "transport": {
20
20
  "type": "stdio"
@@ -21,6 +21,6 @@ evidence stays local unless the operator explicitly enables protected export
21
21
  for a trusted client in the same data boundary.
22
22
  """
23
23
 
24
- __version__ = "2.1.0"
24
+ __version__ = "2.2.0"
25
25
 
26
26
  __all__ = ["__version__"]
@@ -169,7 +169,9 @@ class AgentBridge:
169
169
  sandbox: Optional[Any] = None,
170
170
  mode: Optional[str] = None,
171
171
  store: Optional[RunStore] = None,
172
+ repair_desk: Optional[Any] = None,
172
173
  ):
174
+ self.repair_desk = repair_desk
173
175
  self.bundles_dir = Path(bundles_dir) if bundles_dir is not None else None
174
176
  self.sandbox = sandbox
175
177
  self.allow_run = allow_run or sandbox is not None
@@ -210,7 +212,7 @@ class AgentBridge:
210
212
  for entry in sandbox.entries():
211
213
  self.catalog[entry.name] = entry
212
214
  else:
213
- engine = FlowCliEngine(self.runner, self.attended)
215
+ engine = FlowCliEngine(self.runner, self.attended, repair_desk=repair_desk)
214
216
  for workflow_id, info in self.workflows.items():
215
217
  card = card_for_bundle(info, default_name=workflow_id)
216
218
  name = card.name
@@ -241,6 +243,7 @@ class AgentBridge:
241
243
  mode=self.mode,
242
244
  allow_run=self.allow_run,
243
245
  require_all_inputs=not self.allow_recorded_defaults,
246
+ repair_desk=self.repair_desk,
244
247
  )
245
248
  return self._service
246
249
 
@@ -347,8 +350,81 @@ class AgentBridge:
347
350
  usage=_GET_RUN_USAGE,
348
351
  )
349
352
  )
353
+ specs.extend(self._repair_specs())
350
354
  return specs
351
355
 
356
+ def _repair_tools_enabled(self) -> bool:
357
+ from openadapt_agent.repair import teach_supported
358
+
359
+ desk = self.repair_desk
360
+ return bool(
361
+ self.allow_run
362
+ and desk is not None
363
+ and desk.demonstrator is not None
364
+ and teach_supported()
365
+ and desk.policy.any_enabled()
366
+ )
367
+
368
+ def _repair_specs(self) -> list[ToolSpec]:
369
+ """Listed only after a person turned on screen access for repairs."""
370
+ if not self._repair_tools_enabled():
371
+ return []
372
+ token = {
373
+ "type": "string",
374
+ "minLength": 16,
375
+ "maxLength": 128,
376
+ "description": "repair_token from a needs_repair result.",
377
+ }
378
+ return [
379
+ ToolSpec(
380
+ name="get_repair_screen",
381
+ description=(
382
+ "Get the screen where a run stopped, as a PNG, so you can "
383
+ "decide what one click gets past that step. Personal data "
384
+ "is removed first unless the operator chose otherwise. "
385
+ "Refused when the operator didn't turn on screen access, "
386
+ "when a person hasn't approved sharing yet, or when "
387
+ "scrubbing fails."
388
+ ),
389
+ input_schema={
390
+ "type": "object",
391
+ "properties": {"repair_token": token},
392
+ "required": ["repair_token"],
393
+ "additionalProperties": False,
394
+ },
395
+ annotations=_READ_ONLY_ANNOTATIONS,
396
+ ),
397
+ ToolSpec(
398
+ name="teach_repair",
399
+ description=(
400
+ "Show OpenAdapt the one click that gets a halted run past "
401
+ "its stuck step. OpenAdapt performs and records the click "
402
+ "and proposes a repair. You can't approve it: a person "
403
+ "reviews and tests it first, and until then the workflow "
404
+ "still stops at that step. Each repair_token works once."
405
+ ),
406
+ input_schema={
407
+ "type": "object",
408
+ "properties": {
409
+ "repair_token": token,
410
+ "demonstration": {
411
+ "type": "object",
412
+ "properties": {
413
+ "action": {"type": "string", "enum": ["click"]},
414
+ "x": {"type": "integer", "minimum": 0},
415
+ "y": {"type": "integer", "minimum": 0},
416
+ },
417
+ "required": ["action", "x", "y"],
418
+ "additionalProperties": False,
419
+ },
420
+ },
421
+ "required": ["repair_token", "demonstration"],
422
+ "additionalProperties": False,
423
+ },
424
+ annotations=_RUN_ANNOTATIONS,
425
+ ),
426
+ ]
427
+
352
428
  def list_tool_specs(self) -> list[ToolSpec]:
353
429
  specs = self._contract_specs()
354
430
  if not self.legacy_tools:
@@ -513,6 +589,19 @@ class AgentBridge:
513
589
  )
514
590
  except ServiceError as exc:
515
591
  raise BridgeError(str(exc)) from exc
592
+ if name in ("get_repair_screen", "teach_repair"):
593
+ if not self._repair_tools_enabled():
594
+ raise BridgeError("repair tools are off on this server")
595
+ from openadapt_agent.repair import RepairError
596
+
597
+ try:
598
+ if name == "get_repair_screen":
599
+ return self.repair_desk.screen(arguments.get("repair_token"))
600
+ return self.repair_desk.teach(
601
+ arguments.get("repair_token"), arguments.get("demonstration")
602
+ )
603
+ except RepairError as exc:
604
+ raise BridgeError(str(exc)) from exc
516
605
  if not self.legacy_tools:
517
606
  raise BridgeError("unknown tool name")
518
607
  if name == "get_workflow":