Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b040c65
wip: snapshot of interrupted build track
abrichr Oct 9, 2026
22abad6
feat: finish the four-outcome result contract
abrichr Oct 9, 2026
c4692db
fix: report verified runs as done and uncertain saves as check-the-re…
abrichr Oct 9, 2026
ca37cbc
feat: add workflow cards with typed inputs for agents
abrichr Oct 9, 2026
ca6e613
test: keep card tests valid on the Flow 1.26 floor
abrichr Oct 9, 2026
497ba04
test: keep card tests valid on the Flow 1.26 floor
abrichr Oct 9, 2026
2d2c54a
feat: keep a durable record per run and gate request_id with Flow's l…
abrichr Oct 9, 2026
35bdc46
feat: add run_workflow and get_run with typed results over MCP
abrichr Oct 9, 2026
068838d
feat: serve a zero-flag sandbox and one --mode flag
abrichr Oct 9, 2026
c107537
test: drive every sandbox case through real openadapt-flow
abrichr Oct 9, 2026
4dec4b2
feat: export PHI-safe Agent Skills with one description per workflow
abrichr Oct 9, 2026
658dd1f
fix: resolve the runs root before asking Flow for a run's review state
abrichr Oct 9, 2026
0f71b08
chore: pin Python in the documented commands; smoke-test any launch line
abrichr Oct 9, 2026
dee817a
fix: label simulated sandbox results up front
abrichr Oct 9, 2026
c7d34df
docs: lead the README with the partner contract
abrichr Oct 9, 2026
7067a4f
docs: describe the result contract, modes, and sandbox in DESIGN
abrichr Oct 9, 2026
cf055b2
chore: require Flow 1.29 for the tutorial extra; refresh docstrings
abrichr Oct 9, 2026
67e2e7c
fix: check request_id in the service and close a replay race
abrichr Oct 9, 2026
2aefd33
fix: never mark a run interrupted while its worker is finishing
abrichr Oct 9, 2026
ce010ed
fix: address review findings in the run gate, CLI, and records
abrichr Oct 9, 2026
089cf52
fix: never call a run that started a launch failure
abrichr Oct 9, 2026
9b15f3e
fix: say the app isn't reachable when it isn't
abrichr Oct 9, 2026
bba100d
docs: refresh the measured sandbox results
abrichr Oct 9, 2026
f570241
fix: label a server that answers paused runs as attended
abrichr Oct 9, 2026
b07cc68
fix: refuse a request_id reused for other inputs during a race
abrichr Oct 9, 2026
f73a6dc
fix: never report a starting run as interrupted to a concurrent retry
abrichr Oct 10, 2026
995caa2
docs: lead with the install line that works today and link the baselines
abrichr Oct 10, 2026
f41eb73
docs: describe today's checks without guessing and give the sandbox p…
abrichr Oct 10, 2026
a0bfa86
fix: never turn a reviewed run into a retryable result on get_run
abrichr Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,24 @@ jobs:
python scripts/check_release_artifacts.py dist
python scripts/check_dist.py dist/*
python scripts/check_source_boundary.py --require-dist

sandbox-e2e:
# The zero-flag sandbox with real openadapt-flow, a real browser, and the
# synthetic MockMed app: no fakes. Unit jobs above stub Flow's CLI.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"
cache: pip
- name: Install with the browser extra
run: pip install -e ".[dev,tutorial]"
- name: Install Chromium
run: python -m playwright install --with-deps chromium
- name: Run every sandbox case through openadapt-flow
env:
OPENADAPT_AGENT_E2E: "1"
DO_NOT_TRACK: "1"
OPENADAPT_TELEMETRY: "0"
run: pytest -q tests/test_sandbox_flow.py
289 changes: 177 additions & 112 deletions README.md

Large diffs are not rendered by default.

302 changes: 213 additions & 89 deletions docs/DESIGN.md

Large diffs are not rendered by default.

32 changes: 18 additions & 14 deletions docs/DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ https://openadapt.ai/production-lifecycle.json (seven target admissions,
https://openadapt.ai/production-workflow-admissions.json (seven synthetic
admissions, `0.0.0-synthetic`). That isn't a customer job. Standard and
Regulated need an active workflow admission for the exact bundle version.
Demo and the synthetic tutorial may run without one.
Demo and the synthetic sandbox may run without one.

This document describes how the package is made installable and
discoverable as an MCP server, the security-relevant distinction
Expand All @@ -27,7 +27,7 @@ server". Keep them separate.
| **What it is** | the `openadapt-agent` package and its MCP server *program* | a user's compiled `openadapt-flow` workflow *bundle* |
| **What it exposes** | PHI-safe workflow and Needs Attention projections plus opt-in governed run and attended-action tools | one customer's specific recorded workflow (its steps, parameters, recorded example values) |
| **Where it lives** | PyPI + MCP registries (official, Smithery, mcp.so, Glama, PulseMCP) | the operator's own disk, passed at launch via `--bundles` |
| **Ships in the package?** | yes — code only | **never** — no bundle is embedded in the wheel, `server.json`, or any registry listing. `serve --tutorial` generates the public synthetic MockMed bundle at serve time. |
| **Ships in the package?** | yes — code only | **never** — no bundle is embedded in the wheel, `server.json`, or any registry listing. The zero-flag sandbox generates its synthetic MockMed workflow at serve time. |

**Design consequence:** the published server takes the bundle directory
as a launch-time argument (`--bundles <dir>`) and reads nothing about a
Expand All @@ -38,12 +38,13 @@ inside their trust boundary. The registry listing advertises the
*capability*, not any workflow.

**Read-only by default when registry-launched.** The official `server.json`
omits `--allow-run`, and the MCPB `manifest.json` leaves both execution and
attended actions off until the operator enables them in the install form. A
default install yields PHI-safe inspection and Needs Attention tools only
(`list_workflows`, `get_workflow`, `get_run_report`,
`list_needs_attention`, and `get_attention_item`). Enabling workflow runs
adds the dynamic `run_workflow_<opaque-id>` tools; enabling attended actions
omits `--mode` and `--allow-run`, and the MCPB `manifest.json` leaves both
execution and attended actions off until the operator enables them in the
install form. A default install yields PHI-safe inspection and Needs
Attention tools only (`list_workflows`, `get_run`, `get_workflow`,
`get_run_report`, `list_needs_attention`, and `get_attention_item`).
Enabling workflow runs adds `run_workflow` (the request-id contract) and the
deprecated per-workflow `run_workflow_<opaque-id>` tools; enabling attended actions
adds Reject, Teach, and Escalate, while a qualified deployment config also makes
Continue and Skip available. Clients without MCP form elicitation use Flow's
attended console/CLI, where all five capabilities remain available. This
Expand All @@ -70,21 +71,24 @@ truth; the machine-readable copies are [`../server.json`](../server.json)
and [`../manifest.json`](../manifest.json).

- **Name (reverse-DNS, official registry):** `io.github.OpenAdaptAI/openadapt-agent`
- **Display name:** OpenAdapt Agent (openadapt-flow bridge)
- **Display name:** OpenAdapt: your agent decides, OpenAdapt enters it and checks it saved
- **PyPI package:** `openadapt-agent`
- **Version:** `2.0.2` (staged in this repository; `2.0.1` is the newest version published to PyPI)
- **Description:** OpenAdapt compiles demonstrated GUI workflows into programs. This package invokes them over MCP.
- **Description:** Your AI agent decides what to enter. OpenAdapt enters it in the app and checks that it saved.
- **Homepage / docs:** https://docs.openadapt.ai
- **Repository:** https://github.com/OpenAdaptAI/openadapt-agent
- **License:** MIT
- **Transport:** stdio
- **Run command (uvx):** `uvx --from 'openadapt-agent[tutorial]' openadapt-agent serve --allow-run`
- **Config:** `--allow-run` with no `--bundles` (public synthetic bundle, generated at serve time), `--tutorial` (same path without implying run tools), `--authoring` (local Claude Code first demo; not the published registry recipe), `authoring connect` (outbound hosted mailbox; not the registry recipe), or `--bundles` (operator's private artifact), `--runs-dir`, `--allow-attended-actions`, qualified `--config` for Continue/Skip, and optional secret `OPENADAPT_BUNDLE_KEY`
- **Run command (uvx):** `uvx --python 3.12 openadapt-agent serve` (the sandbox; from GitHub before 2.0.2 is on PyPI: `uvx --python 3.12 --from git+https://github.com/OpenAdaptAI/openadapt-agent openadapt-agent serve`)
- **Config:** no flags (the synthetic sandbox; `--allow-run` and `--tutorial` without `--bundles` are older names for it), `--mode production --bundles <dir>` (run the operator's private workflows), `--mode attended` (also answer paused runs), `--bundles` alone (read-only), `--authoring` (local Claude Code first demo; not the published registry recipe), `authoring connect` (outbound hosted mailbox; not the registry recipe), `--runs-dir`, `--allow-attended-actions`, qualified `--config` for Continue/Skip, and optional secret `OPENADAPT_BUNDLE_KEY`
- **Tools:**
- `list_workflows` / `get_workflow` — PHI-safe structural bundle projections with opaque IDs.
- `list_workflows` — one card per workflow: name, purpose, what done means, typed inputs.
- `run_workflow` — one request with the caller's `request_id`; returns `done` | `needs_review` | `not_sure_if_saved` | `did_not_run` (or `running` with a `run_id`), with `outputSchema` and `structuredContent`. The same `request_id` never writes twice.
- `get_run` — the result of a run by `run_id`.
- `get_workflow` — PHI-safe structural bundle projection by opaque id.
- `get_run_report` — PHI-safe status and count summary; raw evidence stays local unless protected export was explicitly enabled.
- `list_needs_attention` / `get_attention_item` — PHI-safe durable-pause cards and current signed-capability metadata.
- `run_workflow_<opaque-id>` — execute through the governed `openadapt-flow run` CLI when `--allow-run`; returns `success` | `halt` | `refused` | `timeout` | `error`.
- `run_workflow_<opaque-id>` — deprecated; executes through the governed `openadapt-flow run` CLI and returns `success` | `halt` | `refused` | `timeout` | `error` plus the contract fields. A verified run is `success` with `proof: "local"`.
- `continue_attention` / `skip_attention` / `reject_attention` / `teach_attention` / `escalate_attention` — exact, elicited attended decisions under Flow's capability, idempotency, verification, and audit contract.
- **Categories/tags:** mcp, agent-skills, automation, workflow, gui, governed, healthcare, rpa

Expand Down
262 changes: 262 additions & 0 deletions docs/examples/sandbox-results.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
{
"captured_at": "2026-10-09T22:46:34+00:00",
"engine": "flow",
"provenance": "Measured on synthetic data: openadapt-agent sandbox, flow engine, MockMed synthetic clinic app. No real records.",
"results": {
"app_offline": {
"contract_version": "openadapt.run-result/v1",
"label": "Didn't run",
"mode": "sandbox",
"next_action": "Try again with the same request_id once the app is back. If it keeps happening, tell the operator.",
"next_step": "retry",
"outcome": "did_not_run",
"proof": "none",
"reason": "app_unreachable",
"record_changed": "no",
"request_id": "demo-app-offline",
"run_id": "run-6314b8b10b93421899360217",
"safe_to_retry": true,
"sandbox": {
"case": "app_offline",
"engine": "flow"
},
"what_happened": "Didn't start because the app isn't reachable from this computer. Nothing was written. This was a sandbox run on a synthetic app; no real record was touched.",
"workflow": "add_triage_note"
},
"duplicate_record": {
"contract_version": "openadapt.run-result/v1",
"label": "Stopped before saving",
"mode": "sandbox",
"model_calls": 0,
"needs_attention_id": "77d744a682594d44a3f8810d",
"next_action": "A person reviews this run on the computer that ran it. Call get_run later to see their decision. Don't start another run for this work until they finish.",
"next_step": "wait_for_person",
"outcome": "needs_review",
"proof": "none",
"reason": "record_not_confirmed",
"record_changed": "no",
"request_id": "demo-duplicate-record",
"run_id": "run-0b5813cc521e4854a6c3ffa3",
"safe_to_retry": false,
"sandbox": {
"case": "duplicate_record",
"engine": "flow"
},
"seconds": 1.2,
"technical": {
"execution_outcome": "HALTED",
"transaction_outcome": "HALTED_BEFORE_EFFECT"
},
"what_happened": "Stopped before saving. It couldn't confirm it had the right record open, so nothing was written. This was a sandbox run on a synthetic app; no real record was touched.",
"workflow": "add_triage_note"
},
"false_saved_banner": {
"contract_version": "openadapt.run-result/v1",
"label": "Check the record",
"mode": "sandbox",
"model_calls": 0,
"needs_attention_id": "77917c25aa8da46d4de2826a",
"next_action": "Have a person check the record in the app before anything runs again. Don't retry this request.",
"next_step": "check_record",
"outcome": "not_sure_if_saved",
"proof": "none",
"reason": "save_not_confirmed",
"record_changed": "unknown",
"request_id": "demo-false-saved-banner",
"run_id": "run-5b151caa473e4ab292d7e108",
"safe_to_retry": false,
"sandbox": {
"case": "false_saved_banner",
"engine": "flow"
},
"seconds": 9.8,
"technical": {
"execution_outcome": "HALTED",
"transaction_outcome": "RECONCILIATION_REQUIRED"
},
"what_happened": "OpenAdapt can't confirm whether the change was saved. It may have gone through. This was a sandbox run on a synthetic app; no real record was touched.",
"workflow": "add_triage_note"
},
"invalid_input": {
"contract_version": "openadapt.run-result/v1",
"invalid_inputs": [
{
"input": "note",
"problem": "is too short"
}
],
"label": "Didn't run",
"mode": "sandbox",
"next_action": "Fix the inputs listed in invalid_inputs, then send the request again. You can reuse the same request_id.",
"next_step": "fix_inputs",
"outcome": "did_not_run",
"proof": "none",
"reason": "invalid_input",
"record_changed": "no",
"request_id": "demo-invalid",
"run_id": "run-9d88393a00f54898a3a0af9c",
"safe_to_retry": true,
"what_happened": "Didn't start because some inputs aren't valid. Nothing was written. This was a sandbox run on a synthetic app; no real record was touched.",
"workflow": "add_triage_note"
},
"list_workflows": {
"how_to_run": "Call run_workflow with a workflow name from this list, its inputs, and your own request_id for this piece of work. Only outcome done means the change was saved and checked. Never retry not_sure_if_saved. Reuse the same request_id when you retry anything.",
"mode": "sandbox",
"run_tools_enabled": true,
"sandbox": {
"app": "MockMed, a synthetic clinic app with made-up patients",
"cases": [
{
"case": "normal",
"outcome": "done",
"what_it_shows": "The app saves the note and the record check finds it in the app's record store."
},
{
"case": "duplicate_record",
"outcome": "needs_review",
"what_it_shows": "The queue lists the same referral twice, so OpenAdapt can't be sure which record to open. It stops before saving."
},
{
"case": "false_saved_banner",
"outcome": "not_sure_if_saved",
"what_it_shows": "The app shows a saved message, but its record store rejected the note. The record check doesn't find it."
},
{
"case": "timeout_after_save",
"outcome": "not_sure_if_saved",
"what_it_shows": "The record store saves the note, then the app times out and shows an error. Only a person checking the record can tell it saved."
},
{
"case": "app_offline",
"outcome": "did_not_run",
"what_it_shows": "The app isn't reachable, so OpenAdapt doesn't start."
}
],
"engine": "flow",
"engine_detail": "Each run drives the synthetic app in a hidden browser with the real OpenAdapt engine. The first run waits while OpenAdapt records the workflow once.",
"ready": false
},
"workflows": [
{
"available": true,
"changes_records": true,
"done_means": "The note is in the synthetic app's record store, confirmed by reading the store directly instead of trusting the screen.",
"inputs": {
"additionalProperties": false,
"properties": {
"note": {
"description": "The triage note to add. Use made-up text.",
"maxLength": 500,
"minLength": 1,
"type": "string"
},
"sandbox_case": {
"default": "normal",
"description": "Which situation the synthetic app shows (normal: done; duplicate_record: needs_review; false_saved_banner: not_sure_if_saved; timeout_after_save: not_sure_if_saved; app_offline: did_not_run).",
"enum": [
"normal",
"duplicate_record",
"false_saved_banner",
"timeout_after_save",
"app_offline"
],
"type": "string"
}
},
"required": [
"note"
],
"type": "object"
},
"name": "add_triage_note",
"purpose": "Sandbox: add a triage note to the first referral in a synthetic clinic app. Nothing real changes."
}
]
},
"normal": {
"contract_version": "openadapt.run-result/v1",
"label": "Done and checked",
"mode": "sandbox",
"model_calls": 0,
"next_action": "Nothing more to do for this request. Don't run it again for the same work.",
"next_step": "none",
"outcome": "done",
"proof": "local",
"reason": "saved_and_checked",
"record_changed": "yes",
"request_id": "demo-normal",
"run_id": "run-8c28f0bcac224af9b6947a7d",
"safe_to_retry": false,
"sandbox": {
"case": "normal",
"engine": "flow"
},
"seconds": 5.0,
"technical": {
"execution_outcome": "VERIFIED",
"transaction_outcome": "VERIFIED"
},
"what_happened": "Saved the change and read the record back to confirm it. The check ran on this computer, so there's no signed receipt. This was a sandbox run on a synthetic app; no real record was touched.",
"workflow": "add_triage_note"
},
"normal_again_same_request_id": {
"contract_version": "openadapt.run-result/v1",
"label": "Done and checked",
"mode": "sandbox",
"model_calls": 0,
"next_action": "Nothing more to do for this request. Don't run it again for the same work.",
"next_step": "none",
"outcome": "done",
"proof": "local",
"reason": "saved_and_checked",
"record_changed": "yes",
"replayed": true,
"request_id": "demo-normal",
"run_id": "run-8c28f0bcac224af9b6947a7d",
"safe_to_retry": false,
"sandbox": {
"case": "normal",
"engine": "flow"
},
"seconds": 5.0,
"technical": {
"execution_outcome": "VERIFIED",
"transaction_outcome": "VERIFIED"
},
"what_happened": "Saved the change and read the record back to confirm it. The check ran on this computer, so there's no signed receipt. This was a sandbox run on a synthetic app; no real record was touched.",
"workflow": "add_triage_note"
},
"timeout_after_save": {
"contract_version": "openadapt.run-result/v1",
"label": "Check the record",
"mode": "sandbox",
"model_calls": 0,
"needs_attention_id": "481486193ef0e44c0e2b0dd6",
"next_action": "Have a person check the record in the app before anything runs again. Don't retry this request.",
"next_step": "check_record",
"outcome": "not_sure_if_saved",
"proof": "none",
"reason": "save_not_confirmed",
"record_changed": "unknown",
"request_id": "demo-timeout-after-save",
"run_id": "run-cef0b138a5e1488abbd805f5",
"safe_to_retry": false,
"sandbox": {
"case": "timeout_after_save",
"engine": "flow"
},
"seconds": 10.2,
"technical": {
"execution_outcome": "HALTED",
"transaction_outcome": "RECONCILIATION_REQUIRED"
},
"what_happened": "OpenAdapt can't confirm whether the change was saved. It may have gone through. This was a sandbox run on a synthetic app; no real record was touched.",
"workflow": "add_triage_note"
}
},
"versions": {
"openadapt-agent": "2.0.2",
"openadapt-flow": "1.35.1",
"python": "3.12.13"
}
}
Loading
Loading