Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,18 @@ this repository doesn't reimplement them.

## Run it

A solutions engineer gets `VERIFIED` on synthetic MockMed, then hands an agent
the run tool:

```bash
python -m pip install --upgrade openadapt
openadapt quickstart
openadapt-agent serve --allow-run
```

Python 3.10 through 3.12. No account, no API key, no extra. Chromium downloads
itself the first time a browser action runs.
itself the first time a browser action runs. `openadapt-agent` is in the base
install.

`quickstart` records and compiles a task in MockMed, a synthetic
practice-management fixture, certifies it against the shipped clinical-write
Expand Down Expand Up @@ -68,7 +73,12 @@ openadapt quickstart --break-it
Same certified bundle. This time the backend rejects the write after the app
has already painted its success banner, so every on-screen check passes and the
run halts anyway, because the independent read of the record store disagrees.
That halt is the whole product in one command.
The store is unchanged. That halt is the whole product in one command.

`openadapt-agent serve --allow-run` then generates the public MockMed bundle
at serve time and keeps the app up. A client calls `run_local_quickstart`.
You'll get a receipt. You never see frames. Local unsigned success isn't a
Seal; treat unsigned production success as failure.

Inspect what compiled, and what it failed to cover:

Expand Down
12 changes: 9 additions & 3 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,18 @@ installed `openadapt-flow` engine.
## First run

```bash
python -m pip install --upgrade openadapt
openadapt quickstart [--headed] [--break-it] [--out NEW_DIRECTORY]
openadapt-agent serve --allow-run
```

`quickstart` runs the bundled synthetic workflow from recording through an
independently verified Standard-profile result. It refuses to overwrite an
existing output directory.
`quickstart` runs the bundled synthetic MockMed workflow from recording through
an independently verified Standard-profile result. It refuses to overwrite an
existing output directory. `--break-it` reruns the same certified bundle
against a lying backend; the independent oracle must HALT and leave the store
unchanged. `openadapt-agent serve --allow-run` then generates the public
MockMed bundle at serve time and hosts it over MCP. A client POSTs authorized
work and gets a receipt. Frames stay on this machine.

## Flow lifecycle

Expand Down
8 changes: 8 additions & 0 deletions openadapt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def main():
Quick Start:
python -m pip install --upgrade openadapt
openadapt quickstart
openadapt-agent serve --allow-run

\b
Manual Demo Lifecycle:
Expand All @@ -74,6 +75,7 @@ def main():

The manual demo is runnable but not certified for consequential work.
Use `openadapt quickstart` for the effect-verified first run.
`openadapt-agent serve --allow-run` then exposes run_* tools over MCP.
"""
pass

Expand Down Expand Up @@ -253,6 +255,7 @@ def quickstart(
"the screen claimed success and the system of record disagreed."
)
click.echo(f"Caught-fault evidence: {root / 'run-broken' / 'REPORT.md'}")
click.echo("The independent oracle kept the store unchanged.")
click.echo(f"Inspect qualification gaps: openadapt flow lint {root / 'bundle'}")
if not break_it:
click.echo(
Expand All @@ -263,6 +266,9 @@ def quickstart(
"See a fail-safe halt: openadapt flow replay "
f"{root / 'bundle'} --drift modal --run-dir {root}-halt"
)
click.echo("Give a local agent the run tool (synthetic MockMed, no frames):")
click.echo(" openadapt-agent serve --allow-run")
click.echo("A client POSTs authorized work and gets a receipt. Frames stay here.")
click.echo(
"Connect this computer when you want Cloud history and collaboration: "
"https://app.openadapt.ai/dashboard/settings/ingest"
Expand Down Expand Up @@ -974,6 +980,7 @@ def version():

packages = [
"openadapt",
"openadapt-agent",
"openadapt-capture",
"openadapt-ml",
"openadapt-evals",
Expand Down Expand Up @@ -1058,6 +1065,7 @@ def doctor(backend: str | None):
click.echo("\nCore packages (installed with `pip install openadapt`):")
core = [
"openadapt_flow",
"openadapt_agent",
"playwright",
]
for pkg in core:
Expand Down
3 changes: 3 additions & 0 deletions platform-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,9 @@
"compatibility": {
"python": ">=3.10,<3.13",
"launcher_requires": {
"openadapt-agent": [
">=2.0.1,<3"
],
"openadapt-capture": [
">=1.2.0,<2.0.0"
],
Expand Down
10 changes: 9 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ classifiers = [
dependencies = [
"click>=8.0.0",
"openadapt-flow[browser,hosted]>=1.29.0,<2.0.0",
# Day-1 partner kit: `pip install openadapt` must expose
# `openadapt-agent serve --allow-run` without a second package.
"openadapt-agent>=2.0.1,<3",
]

[project.optional-dependencies]
Expand Down Expand Up @@ -92,6 +95,11 @@ privacy = [
flow = [
"openadapt-flow>=1.29.0,<2.0.0",
]
# `openadapt-agent` also ships in the base install. Keep the extra so
# `pip install openadapt[agent]` still resolves.
agent = [
"openadapt-agent>=2.0.1,<3",
]
# Bundles
core = [
"openadapt[capture,ml,evals,viewer]",
Expand All @@ -100,7 +108,7 @@ all = [
# Cross-platform clients are always safe to resolve. Native bindings remain
# conditional so `openadapt[all]` never pulls PyObjC onto Linux/Windows or
# PyGObject onto macOS/Windows.
"openadapt[browser,core,grounding,retrieval,privacy,flow,windows,rdp]",
"openadapt[browser,core,grounding,retrieval,privacy,flow,windows,rdp,agent]",
"openadapt[macos]; sys_platform == 'darwin'",
"openadapt[linux]; sys_platform == 'linux'",
]
Expand Down
12 changes: 10 additions & 2 deletions tests/test_cli_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ def test_quickstart_runs_one_local_lifecycle_without_overwriting(monkeypatch):
assert calls[0][0] == "tutorial"
assert calls[0][1] == "--out"
assert calls[0][3:] == ["--name", "local-quickstart"]
assert "openadapt-agent serve --allow-run" in result.output
assert "Frames stay here" in result.output


def test_quickstart_forwards_the_headed_tutorial_option(monkeypatch):
Expand Down Expand Up @@ -136,6 +138,8 @@ def test_quickstart_forwards_the_break_it_option_and_names_the_evidence(
assert calls[0][-1] == "--break-it"
assert "run-broken" in result.output
assert "HALTED" in result.output
assert "store unchanged" in result.output
assert "openadapt-agent serve --allow-run" in result.output


def test_quickstart_restores_the_operator_scrub_setting(monkeypatch):
Expand Down Expand Up @@ -239,8 +243,9 @@ def test_doctor_lists_quickstart_dependencies_as_core_not_extras():
core_section = out[core_idx:optional_idx]
optional_section = out[optional_idx:]

# Flow and the browser tutorial driver are core.
# Flow, the agent bridge, and the browser tutorial driver are core.
assert "openadapt_flow" in core_section
assert "openadapt_agent" in core_section
assert "playwright" in core_section
assert "playwright" not in optional_section
# The excluded-by-default extras must appear only in the optional
Expand Down Expand Up @@ -270,7 +275,9 @@ def test_launcher_flow_and_substrate_extras_metadata():
metadata["dependencies"].count("openadapt-flow[browser,hosted]>=1.29.0,<2.0.0")
== 1
)
assert metadata["dependencies"].count("openadapt-agent>=2.0.1,<3") == 1
assert extras["flow"] == ["openadapt-flow>=1.29.0,<2.0.0"]
assert extras["agent"] == ["openadapt-agent>=2.0.1,<3"]
assert extras["browser"] == ["openadapt-flow[browser]>=1.29.0,<2.0.0"]
assert extras["privacy"] == ["openadapt-flow[privacy]>=1.29.0,<2.0.0"]
assert extras["capture"] == [
Expand All @@ -286,7 +293,7 @@ def test_launcher_flow_and_substrate_extras_metadata():
]
assert extras["rdp"] == ["openadapt-flow[rdp]>=1.29.0,<2.0.0"]
assert extras["all"] == [
"openadapt[browser,core,grounding,retrieval,privacy,flow,windows,rdp]",
"openadapt[browser,core,grounding,retrieval,privacy,flow,windows,rdp,agent]",
"openadapt[macos]; sys_platform == 'darwin'",
"openadapt[linux]; sys_platform == 'linux'",
]
Expand Down Expand Up @@ -465,6 +472,7 @@ def test_top_level_help_leads_with_flow():
# Quick Start headline and Commands listing both lead with flow.
assert "openadapt flow demo-record" in result.output
assert "openadapt quickstart" in result.output
assert "openadapt-agent serve --allow-run" in result.output
assert "effect-verified first run" in result.output
assert "Standalone local human GUI capture" in result.output
assert "Research: evaluate" in result.output
Expand Down
73 changes: 73 additions & 0 deletions tests/test_partner_kit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Day-1 partner kit: invoke Flow, keep the MockMed oracle honest.

Launcher unit tests mock ``_invoke_flow``. That is OpenAdapt#1122-class
coverage: it proves argv forwarding, not that Flow ran. These tests call
the installed engine. The weekly ``quickstart-lifecycle`` job still owns
the full Playwright ``VERIFIED`` path.
"""

from __future__ import annotations

import io
import json
from contextlib import redirect_stderr, redirect_stdout
from urllib.error import HTTPError
from urllib.request import Request, urlopen

import pytest

from openadapt.cli import _invoke_flow


def test_invoke_flow_runs_the_installed_tutorial_help() -> None:
pytest.importorskip("openadapt_flow", reason="openadapt-flow not installed")
buffer = io.StringIO()
with redirect_stdout(buffer), redirect_stderr(buffer):
try:
code = _invoke_flow(["tutorial", "--help"])
except SystemExit as exc:
code = int(exc.code or 0)
text = buffer.getvalue()
assert code == 0, text
assert "--break-it" in text


def test_mockmed_optimistic_fault_leaves_the_store_unchanged() -> None:
"""--break-it injects this fault. The independent GET /api/db must not move."""
pytest.importorskip("openadapt_flow", reason="openadapt-flow not installed")
from openadapt_flow.mockmed.fault_server import serve

url, db, stop = serve()
try:
before = json.loads(urlopen(url + "api/db", timeout=2).read().decode("utf-8"))
assert before["records"] == []

request = Request(
url + "api/encounter?fault=optimistic",
data=json.dumps(
{"patient_id": "p1", "type": "Triage", "note": "synthetic"}
).encode("utf-8"),
method="POST",
headers={"Content-Type": "application/json"},
)
with pytest.raises(HTTPError) as raised:
urlopen(request, timeout=2)
assert raised.value.code == 409

after = json.loads(urlopen(url + "api/db", timeout=2).read().decode("utf-8"))
assert after["records"] == []
assert db.rejected_writes == 1
control = Request(
url + "api/encounter?fault=ok",
data=json.dumps(
{"patient_id": "p1", "type": "Triage", "note": "synthetic"}
).encode("utf-8"),
method="POST",
headers={"Content-Type": "application/json"},
)
landed = json.loads(urlopen(control, timeout=2).read().decode("utf-8"))
assert landed.get("ok") is True
stored = json.loads(urlopen(url + "api/db", timeout=2).read().decode("utf-8"))
assert len(stored["records"]) == 1
finally:
stop()