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.
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/ci.yml +24 -2
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/codeql.yml +2 -2
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/release.yml +4 -4
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/PKG-INFO +22 -8
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/README.md +17 -5
- openadapt_agent-2.2.0/docs/REPAIR.md +95 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/manifest.json +1 -1
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/pyproject.toml +8 -3
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/server.json +2 -2
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/__init__.py +1 -1
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/bridge.py +90 -1
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/cli.py +144 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/contract.py +29 -0
- openadapt_agent-2.2.0/src/openadapt_agent/repair.py +724 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/runner.py +81 -9
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/service.py +58 -2
- openadapt_agent-2.2.0/tests/test_repair.py +398 -0
- openadapt_agent-2.2.0/tests/test_repair_e2e.py +201 -0
- openadapt_agent-2.2.0/tests/test_repair_hold.py +107 -0
- openadapt_agent-2.2.0/uv.lock +2643 -0
- openadapt_agent-2.1.0/uv.lock +0 -1300
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/CODEOWNERS +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/dependabot.yml +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/dependency-review.yml +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.github/workflows/secret-scan.yml +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.gitignore +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.gitleaks.toml +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/.mcpbignore +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/LICENSE +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/SECURITY.md +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/DESIGN.md +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/DISTRIBUTION.md +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/MAILBOX_CLI.md +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/docs/examples/sandbox-results.json +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/llms.txt +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/capture_sandbox_examples.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_dist.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_mcpb.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_release_artifacts.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/check_source_boundary.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/scripts/smoke_client.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/skills/openadapt-gui-write/SKILL.md +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/source-policy.public.json +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/__main__.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/approval.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/attended.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/authoring.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/bundles.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/cards.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/copy.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/flow_service.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/mailbox.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/mcp.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/mcpb_entry.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/runner_bind.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/runs.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/sandbox.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/seal.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/seal_cli.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/skill.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/src/openadapt_agent/tutorial.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/conftest.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/fixtures/cloud-ts-signed-seal.json +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_approval.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_approval_flow.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_attended_bridge.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_authoring.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_bridge.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_cards.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_cli.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_contract.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_distribution.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_mailbox.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_mcp_server.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_mcpb_entry.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_partner_kit.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_public_lifecycle.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_readme_commands.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_runner.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_runs.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_sandbox.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_sandbox_flow.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_seal.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_seal_cli.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_seal_e2e.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_service.py +0 -0
- {openadapt_agent-2.1.0 → openadapt_agent-2.2.0}/tests/test_skill.py +0 -0
- {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@
|
|
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.
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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@
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
17
|
+
"version": "2.2.0",
|
|
18
18
|
"runtimeHint": "uvx",
|
|
19
19
|
"transport": {
|
|
20
20
|
"type": "stdio"
|
|
@@ -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":
|