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
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,18 @@ target application, API key, or operating-system automation permissions:
openadapt quickstart
```

The tutorial records and compiles a synthetic task, certifies it with the
shipped clinical-write policy, and runs it under the Standard profile. A
separate read-only API confirms the saved record outside the screen that
performed the write. The healthy run returns `VERIFIED` with no model or Cloud
call.

You now have:

- `openadapt-quickstart/recording/`: the demonstrated interaction and retained target evidence
- `openadapt-quickstart/bundle/`: the inspectable compiled workflow
- `openadapt-quickstart/run/REPORT.md`: the ordered actions, evidence, outcome, and any halt reason
- `openadapt-quickstart/run/receipt.json`: the privacy-safe local receipt for the synthetic verified run

Inspect the program and its deployment gaps:

Expand Down
100 changes: 44 additions & 56 deletions openadapt/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
openadapt doctor
"""

import os
import sys
from pathlib import Path
from typing import Optional
Expand Down Expand Up @@ -172,75 +171,64 @@ def _run_flow(argv: list[str]) -> None:
"--headed", is_flag=True, help="Show the browser while the tutorial runs."
)
def quickstart(out: Path, headed: bool) -> None:
"""Run the complete local tutorial: record, compile, check, and replay.
"""Run a verified local tutorial against the bundled synthetic app.

This is the shortest path to a real OpenAdapt run. It uses the bundled
synthetic tutorial, keeps every artifact on this computer, and enables no
model or Cloud call. The output directory is never overwritten.
synthetic tutorial, verifies the write through an independent read-only
system-of-record interface, keeps every artifact on this computer, and
enables no model or Cloud call. The output directory is never overwritten.
"""
import os

root = out.expanduser().resolve()
if root.exists():
raise click.UsageError(
f"Output already exists: {root}. Pass --out with a new directory."
)

recording = root / "recording"
bundle = root / "bundle"
run_dir = root / "run"
steps = [
("Record the tutorial", ["demo-record", "--out", str(recording)]),
(
"Compile the recording",
[
"compile",
str(recording),
"--out",
str(bundle),
"--name",
"local-quickstart",
],
),
(
"Certify the tutorial",
["certify", str(bundle), "--policy", "permissive"],
),
(
"Replay locally",
["replay", str(bundle), "--run-dir", str(run_dir)],
),
argv = [
"tutorial",
"--out",
str(root),
"--name",
"local-quickstart",
]
if headed:
steps[0][1].append("--headed")
steps[-1][1].append("--headed")

for index, (label, argv) in enumerate(steps, start=1):
click.echo(f"\n[{index}/{len(steps)}] {label}")
# The bundled tutorial contains synthetic identity-like strings. Keep
# the ordinary report warning meaningful for real workflows by making
# this one known-safe fixture explicit, then restore the operator's
# environment immediately after replay.
scrub = os.environ.get("OPENADAPT_FLOW_SCRUB")
if argv[0] == "replay" and scrub in (None, "auto"):
os.environ["OPENADAPT_FLOW_SCRUB"] = "off"
try:
code = _invoke_flow(argv)
finally:
if argv[0] == "replay" and scrub in (None, "auto"):
if scrub is None:
os.environ.pop("OPENADAPT_FLOW_SCRUB", None)
else:
os.environ["OPENADAPT_FLOW_SCRUB"] = scrub
if code:
raise click.ClickException(
f"{label} stopped with exit code {code}. "
f"Any completed artifacts remain in {root}."
)
argv.append("--headed")

# The bundled tutorial contains only fixed synthetic data. Keep an
# installed-but-unconfigured privacy provider from blocking this known-safe
# fixture, then restore the operator's setting immediately.
scrub = os.environ.get("OPENADAPT_FLOW_SCRUB")
if scrub in (None, "auto"):
os.environ["OPENADAPT_FLOW_SCRUB"] = "off"
try:
code = _invoke_flow(argv)
finally:
if scrub in (None, "auto"):
if scrub is None:
os.environ.pop("OPENADAPT_FLOW_SCRUB", None)
else:
os.environ["OPENADAPT_FLOW_SCRUB"] = scrub
if code:
raise click.ClickException(
f"The verified tutorial stopped with exit code {code}. "
f"Any completed artifacts remain in {root}."
)

click.echo("\nLocal quickstart complete.")
click.echo(f"Bundle: {bundle}")
click.echo(f"Run evidence: {run_dir}")
click.echo(f"Bundle: {root / 'bundle'}")
click.echo(f"Run evidence: {root / 'run'}")
click.echo("Outcome: VERIFIED under the Standard profile.")
click.echo(
"The synthetic write was confirmed through a read-only system-of-record API."
)
click.echo("No model or Cloud call was enabled.")
click.echo(f"Inspect qualification gaps: openadapt flow lint {bundle}")
click.echo(f"Inspect qualification gaps: openadapt flow lint {root / 'bundle'}")
click.echo(
"See a fail-safe halt: openadapt flow replay "
f"{root / 'bundle'} --drift modal --run-dir {root}-halt"
)
click.echo(
"Connect this computer when you want Cloud history and collaboration: "
"https://app.openadapt.ai/dashboard/settings/ingest"
Expand Down
18 changes: 9 additions & 9 deletions platform-manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"manifest_kind": "openadapt-platform-release-manifest",
"schema_version": "1.0.0",
"generated_at": "2026-07-29T20:44:27+00:00",
"generated_at": "2026-07-30T16:36:43+00:00",
"release_channel": "beta",
"components": {
"launcher": {
Expand All @@ -26,21 +26,21 @@
},
"flow": {
"package": "openadapt-flow",
"version": "1.26.0",
"version": "1.27.0",
"source": "pypi",
"requires_python": "<3.13,>=3.10",
"artifacts": [
{
"type": "bdist_wheel",
"filename": "openadapt_flow-1.26.0-py3-none-any.whl",
"url": "https://files.pythonhosted.org/packages/5d/67/9160ea77b039fd9c4fc6a02035eecf060528c9c74c15e0038f4a1da94b7f/openadapt_flow-1.26.0-py3-none-any.whl",
"sha256": "a6f07f16d06c549b20e21ce54957557a707331ffaaa3b8cadbe2adfb5170bfea"
"filename": "openadapt_flow-1.27.0-py3-none-any.whl",
"url": "https://files.pythonhosted.org/packages/42/e5/c08a34a0fc3ab4d05e37e55db11347fa1eb56fb86082de217f2970c35cd0/openadapt_flow-1.27.0-py3-none-any.whl",
"sha256": "1de9fd1fb89811e2411946fcc61bfbf08e433b746ff6a4008f3294332abb816b"
},
{
"type": "sdist",
"filename": "openadapt_flow-1.26.0.tar.gz",
"url": "https://files.pythonhosted.org/packages/07/6f/d7a0c3deca9ded1569eb94913b1cc8cddba65f6246d6c2075a2031e5c5ff/openadapt_flow-1.26.0.tar.gz",
"sha256": "83326969ca9e48a8fd3448a4ebbbb8c0f0d61e698fe81ce04e86b4031f7634a7"
"filename": "openadapt_flow-1.27.0.tar.gz",
"url": "https://files.pythonhosted.org/packages/8b/28/f29542489f2a45d3c80f28bde06a344e28d090d3e2721c760b2d6ebcba93/openadapt_flow-1.27.0.tar.gz",
"sha256": "2c0c1da300659ea6007d194cc07732e7436b06b2a0cbcf0386256f1064ebc107"
}
]
},
Expand Down Expand Up @@ -95,7 +95,7 @@
">=0.1.0"
],
"openadapt-flow": [
">=1.20.1,<2.0.0"
">=1.25.1,<2.0.0"
],
"openadapt-grounding": [
">=0.1.0"
Expand Down
16 changes: 8 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ classifiers = [
# All other capabilities (capture, ml, evals, ...) remain opt-in extras.
dependencies = [
"click>=8.0.0",
"openadapt-flow[hosted]>=1.20.1,<2.0.0",
"openadapt-flow[hosted]>=1.25.1,<2.0.0",
]

[project.optional-dependencies]
Expand All @@ -49,22 +49,22 @@ browser = [
# separately provisioned executable, not a Python-package dependency.
capture = [
"openadapt-capture>=1.0.4,<2.0.0",
"openadapt-flow[capture]>=1.20.1,<2.0.0",
"openadapt-flow[capture]>=1.25.1,<2.0.0",
]
# Replay substrate dependencies stay separate from capture: recording observes
# the human on the local interactive desktop, while replay may target a WAA
# agent, a native app, or a remote framebuffer.
windows = [
"openadapt-flow[windows]>=1.20.1,<2.0.0",
"openadapt-flow[windows]>=1.25.1,<2.0.0",
]
macos = [
"openadapt-flow[macos]>=1.20.1,<2.0.0; sys_platform == 'darwin'",
"openadapt-flow[macos]>=1.25.1,<2.0.0; sys_platform == 'darwin'",
]
linux = [
"openadapt-flow[linux]>=1.20.1,<2.0.0; sys_platform == 'linux'",
"openadapt-flow[linux]>=1.25.1,<2.0.0; sys_platform == 'linux'",
]
rdp = [
"openadapt-flow[rdp]>=1.20.1,<2.0.0",
"openadapt-flow[rdp]>=1.25.1,<2.0.0",
]
ml = [
"openadapt-ml>=0.2.0",
Expand All @@ -82,13 +82,13 @@ retrieval = [
"openadapt-retrieval>=0.1.0",
]
privacy = [
"openadapt-flow[privacy]>=1.20.1,<2.0.0",
"openadapt-flow[privacy]>=1.25.1,<2.0.0",
]
# `flow` now ships in the base install (see `dependencies` above). This
# extra is kept for backward compatibility so `pip install openadapt[flow]`
# and the `all` bundle still resolve.
flow = [
"openadapt-flow>=1.20.1,<2.0.0",
"openadapt-flow>=1.25.1,<2.0.0",
]
# Bundles
core = [
Expand Down
78 changes: 62 additions & 16 deletions tests/test_cli_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from __future__ import annotations

import ast
import os
import sys
from pathlib import Path

Expand Down Expand Up @@ -87,14 +88,59 @@ def test_quickstart_runs_one_local_lifecycle_without_overwriting(monkeypatch):
result = runner.invoke(cli_main, ["quickstart", "--out", "first-run"])
assert result.exit_code == 0, result.output

assert [call[0] for call in calls] == [
"demo-record",
"compile",
"certify",
"replay",
]
assert calls[2][-1] == "permissive"
assert "No model or Cloud call was enabled" in result.output
assert len(calls) == 1
assert calls[0][0] == "tutorial"
assert calls[0][1] == "--out"
assert calls[0][3:] == ["--name", "local-quickstart"]


def test_quickstart_forwards_the_headed_tutorial_option(monkeypatch):
calls = []
monkeypatch.setattr(
"openadapt.cli._invoke_flow",
lambda argv: calls.append(list(argv)) or 0,
)

runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(
cli_main,
["quickstart", "--headed", "--out", "headed-run"],
)

assert result.exit_code == 0, result.output
assert len(calls) == 1
assert calls[0][0] == "tutorial"
assert calls[0][-1] == "--headed"


def test_quickstart_restores_the_operator_scrub_setting(monkeypatch):
monkeypatch.setenv("OPENADAPT_FLOW_SCRUB", "auto")
monkeypatch.setattr("openadapt.cli._invoke_flow", lambda _argv: 0)

runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli_main, ["quickstart", "--out", "first-run"])

assert result.exit_code == 0, result.output
assert os.environ["OPENADAPT_FLOW_SCRUB"] == "auto"


def test_quickstart_retains_artifacts_when_the_engine_halts(monkeypatch):
def halt_after_writing(argv):
root = Path(argv[argv.index("--out") + 1])
root.mkdir(parents=True)
(root / "halt-evidence.json").write_text("{}")
return 2

monkeypatch.setattr("openadapt.cli._invoke_flow", halt_after_writing)

runner = CliRunner()
with runner.isolated_filesystem():
result = runner.invoke(cli_main, ["quickstart", "--out", "halted-run"])

assert result.exit_code != 0
assert Path("halted-run/halt-evidence.json").is_file()


def test_quickstart_refuses_an_existing_output(monkeypatch):
Expand Down Expand Up @@ -207,22 +253,22 @@ def test_launcher_flow_and_substrate_extras_metadata():
)["project"]
extras = metadata["optional-dependencies"]

assert metadata["dependencies"].count("openadapt-flow[hosted]>=1.20.1,<2.0.0") == 1
assert extras["flow"] == ["openadapt-flow>=1.20.1,<2.0.0"]
assert metadata["dependencies"].count("openadapt-flow[hosted]>=1.25.1,<2.0.0") == 1
assert extras["flow"] == ["openadapt-flow>=1.25.1,<2.0.0"]
assert extras["browser"] == ["playwright>=1.44"]
assert extras["privacy"] == ["openadapt-flow[privacy]>=1.20.1,<2.0.0"]
assert extras["privacy"] == ["openadapt-flow[privacy]>=1.25.1,<2.0.0"]
assert extras["capture"] == [
"openadapt-capture>=1.0.4,<2.0.0",
"openadapt-flow[capture]>=1.20.1,<2.0.0",
"openadapt-flow[capture]>=1.25.1,<2.0.0",
]
assert extras["windows"] == ["openadapt-flow[windows]>=1.20.1,<2.0.0"]
assert extras["windows"] == ["openadapt-flow[windows]>=1.25.1,<2.0.0"]
assert extras["macos"] == [
"openadapt-flow[macos]>=1.20.1,<2.0.0; sys_platform == 'darwin'"
"openadapt-flow[macos]>=1.25.1,<2.0.0; sys_platform == 'darwin'"
]
assert extras["linux"] == [
"openadapt-flow[linux]>=1.20.1,<2.0.0; sys_platform == 'linux'"
"openadapt-flow[linux]>=1.25.1,<2.0.0; sys_platform == 'linux'"
]
assert extras["rdp"] == ["openadapt-flow[rdp]>=1.20.1,<2.0.0"]
assert extras["rdp"] == ["openadapt-flow[rdp]>=1.25.1,<2.0.0"]
assert extras["all"] == [
"openadapt[browser,core,grounding,retrieval,privacy,flow,windows,rdp]",
"openadapt[macos]; sys_platform == 'darwin'",
Expand Down
Loading