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
10 changes: 9 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ jobs:
- name: Install
run: pip install -e ".[dev]"
- name: Lint
run: ruff check src tests
run: ruff check src tests scripts
- name: Test
run: pytest -q
- name: Build release artifacts
if: matrix.python-version == '3.12'
run: python -m build
- name: Verify MIT package boundary
if: matrix.python-version == '3.12'
run: |
python scripts/check_release_artifacts.py dist
python scripts/check_dist.py dist/*
321 changes: 226 additions & 95 deletions README.md

Large diffs are not rendered by default.

421 changes: 256 additions & 165 deletions docs/DESIGN.md

Large diffs are not rendered by default.

42 changes: 24 additions & 18 deletions docs/DISTRIBUTION.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Distribution & discoverability — openadapt-agent

**Status: Experimental (v2).** This document describes how the package is
**Status: Beta (v2).** This document describes how the package is
made installable and discoverable as an MCP server, the security-relevant
distinction between the *public capability* and a *user's private
bundle*, and the exact founder-owned steps to publish and list it. The
Expand All @@ -15,7 +15,7 @@ server". Keep them separate.
| | Public / official | Private / per-user |
| --- | --- | --- |
| **What it is** | the `openadapt-agent` package and its MCP server *program* | a user's compiled `openadapt-flow` workflow *bundle* |
| **What it exposes** | the OpenAdapt capability: inspect a compiled bundle, and (opt-in) run it under governance | one customer's specific recorded workflow (its steps, parameters, recorded example values) |
| **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 |

Expand All @@ -29,12 +29,15 @@ inside their trust boundary. The registry listing advertises the

**Read-only by default when registry-launched.** The `server.json` and
`smithery.yaml` launch the server WITHOUT `--allow-run`, so a one-click
install from a directory yields the inspection tools only
(`list_workflows`, `get_workflow`, `get_run_report`). Executing a
workflow (`run_<slug>`) is a deliberate operator act: they add
`--allow-run` (and typically `--policy`/`--config`) when they run the
server against their own bundles. This matches the security model in
[`DESIGN.md`](DESIGN.md).
install from a directory yields PHI-safe inspection and Needs Attention
tools only (`list_workflows`, `get_workflow`, `get_run_report`,
`list_needs_attention`, and `get_attention_item`). Executing a workflow
(`run_workflow_<opaque-id>`) is a deliberate operator act: they add
`--allow-run` (and typically `--policy`/`--config`). Teach and Escalate
require `--allow-attended-actions`; Continue and Skip additionally require
a qualified deployment `--config`. Clients without MCP form elicitation use
Flow's attended console/CLI, where all four capabilities remain available.
This matches the security model in [`DESIGN.md`](DESIGN.md).

> There is intentionally **no** hosted, multi-tenant "official OpenAdapt
> workflow server" that exposes OpenAdapt-operated workflows to the
Expand All @@ -52,19 +55,20 @@ and [`../smithery.yaml`](../smithery.yaml).
- **Name (reverse-DNS, official registry):** `io.github.OpenAdaptAI/openadapt-agent`
- **Display name:** OpenAdapt Agent (openadapt-flow bridge)
- **PyPI package:** `openadapt-agent`
- **Version:** `2.0.0.dev0` (Experimental; pick the final release version at publish — see step 3.1)
- **Description:** Expose compiled openadapt-flow workflow bundles as governed MCP tools (Experimental).
- **Version:** `2.0.0.dev0` (Beta development release; pick the final release version at publish — see step 3.1)
- **Description:** Local Beta bridge for governed openadapt-flow workflows and attended actions.
- **Homepage / docs:** https://docs.openadapt.ai
- **Repository:** https://github.com/OpenAdaptAI/openadapt-agent
- **License:** MIT
- **Transport:** stdio
- **Run command (uvx):** `uvx openadapt-agent serve --bundles <BUNDLES_DIR> [--allow-run]`
- **Config:** `--bundles` (required, dir), `--allow-run` (opt-in execution), `OPENADAPT_BUNDLE_KEY` (optional secret, for encrypted bundles)
- **Run command (uvx):** `uvx openadapt-agent serve --bundles <BUNDLES_DIR> [--allow-run] [--allow-attended-actions]`
- **Config:** `--bundles` (required), `--runs-dir`, `--allow-run`, `--allow-attended-actions`, qualified `--config` for Continue/Skip, and optional secret `OPENADAPT_BUNDLE_KEY`
- **Tools:**
- `list_workflows` — list exposed bundles (slug, name, params, step count, load errors).
- `get_workflow` — inspect one workflow (step intents, declared params with recorded example values, certification status).
- `get_run_report` — fetch the persisted `report.json` for a prior run (halt/success evidence).
- `run_<slug>` — execute a bundle via the governed `openadapt-flow run` CLI (only when `--allow-run`). Returns `success` | `halt` | `refused` | `timeout` | `error`.
- `list_workflows` / `get_workflow` — PHI-safe structural bundle projections with opaque IDs.
- `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`.
- `continue_attention` / `skip_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

## 3. Release automation vs. founder one-time actions
Expand Down Expand Up @@ -138,7 +142,7 @@ No secret is ever committed; these live only in repo Settings -> Secrets.
`2.0.0.dev0` is a dev pre-release; `pip install openadapt-agent` will not
select it without `--pre`. For a real launch choose one of:
- `2.0.0rc1` (recommended first public: pre-release, opt-in via `--pre`), or
- `2.0.0` (final; drop the Experimental-only caveats only when honest to).
- `2.0.0` (final; remove development-release caveats only when the evidence supports it).

Whatever you choose, set it in **three places that a CI test pins
together**: `pyproject.toml` `version`, `src/openadapt_agent/__init__.py`
Expand Down Expand Up @@ -182,7 +186,9 @@ add the ownership marker it names to the package metadata and re-run.
- "Add Server" -> point at `github.com/OpenAdaptAI/openadapt-agent`.
- Smithery reads `smithery.yaml` (stdio `startCommand`, `configSchema`,
`commandFunction`). Confirm the generated config form shows
`bundlesDir` (required), `allowRun` (default off), `bundleKey` (secret).
`bundlesDir` (required), `runsDir`, `allowRun` (default off),
`allowAttendedActions` (default off), optional `deploymentConfig`,
`headed`, and `bundleKey` (secret).
- Publish. Smithery hosts the connection config; it does not host bundles.

> **Why manual:** Smithery has no public "create listing" API keyed to an
Expand Down
20 changes: 15 additions & 5 deletions llms.txt
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
# openadapt-agent

> The agent-facing bridge for [openadapt-flow](https://github.com/OpenAdaptAI/openadapt-flow). It exposes compiled workflow bundles to other agents (Claude Code, Claude Desktop, any MCP client) as an **MCP server** and as **Agent Skills**. Every execution shells out to the governed `openadapt-flow run` CLI, so flow's fail-closed admission gates (certification, identity arming, effect contracts, encryption at rest, integrity pinning) cannot be bypassed. A halted or refused run is always returned as a structured halt/refusal with an evidence pointer, never as a fabricated success. Status: Experimental (v2).
> The local agent bridge for [openadapt-flow](https://github.com/OpenAdaptAI/openadapt-flow). It exposes compiled workflows and PHI-safe Needs Attention items to MCP clients and emits Agent Skills without becoming a second automation runtime. Healthy execution uses Flow's governed `run` command; a halted or refused run is returned as that exact non-success outcome. Flow remains the authority for policy, identity, verification, durable pauses, attended decisions, repair, and audit. Status: Beta (v2).

## What it provides

- `openadapt-agent serve --bundles <dir> [--allow-run]`: an MCP server (stdio) over a directory of compiled bundles. Tools: `list_workflows`, `get_workflow`, `get_run_report` (always on, read-only) and `run_<slug>` (only when the operator passes `--allow-run`), whose JSON input schema is derived from the bundle's declared parameters.
- `openadapt-agent emit-skill <bundle> --out <dir>`: a Claude Agent Skill folder for one bundle. Wraps flow's own `emit-skill` and appends MCP-invocation and halt-semantics guidance.
- `openadapt-agent serve --bundles <dir> [--allow-run]`: a local MCP stdio server. `list_workflows`, `get_workflow`, `get_run_report`, `list_needs_attention`, and `get_attention_item` are always available as PHI-safe read-only projections. `run_workflow_<opaque-id>` tools require `--allow-run`.
- `--allow-attended-actions` adds exact Teach and Escalate tools for signed durable pauses. With a qualified Flow `--config`, the same server also exposes Continue and Skip through Flow's deployment-bound live verifier and deterministic resume path.
- `openadapt-agent emit-skill <bundle> --out <dir>` wraps Flow's skill emitter and appends MCP, halt, and attended-action guidance.

## Attended actions

- `continue_attention`: after the local operator completes the paused task, Flow verifies the exact outcome and resumes without actuating that task again.
- `skip_attention`: applies only a compiled, non-consequential skip that Flow revalidates at decision time.
- `teach_attention`: records an audited corrective-demonstration request; Flow's revision and regression gates still decide promotion.
- `escalate_attention`: records escalation and preserves the exact durable pause.

All mutations require an exact capability digest, a stable idempotency key, action-specific confirmation, and MCP form elicitation. Clients without form elicitation use Flow's attended console/CLI; the capabilities are not removed or converted to read-only.

## Run outcomes (honesty invariant)

- `success`: exit 0 AND the persisted `report.json` marks the run successful. Only then did the workflow complete.
- `halt`: the run executed and stopped at an unhandled state (halt instead of guessing). NOT a success; evidence in `report.json`/`REPORT.md`.
- `halt`: execution stopped instead of guessing. It is not a success; protected evidence remains in the local operator experience.
- `refused`: a governed admission gate refused the bundle before execution; nothing ran.
- `timeout` / `error`: killed at the deadline / infrastructure problem.

## Public capability vs private artifact

- The **package/MCP server is the public, official OpenAdapt capability**: inspect and (opt-in) run a compiled bundle under governance. It is what gets published to PyPI and listed in MCP registries.
- The **package/MCP server is the public OpenAdapt capability**: inspect and, with explicit operator flags, run or attend a compiled workflow under governance. The v2 package is not yet published.
- A user's **compiled workflow bundle is their private artifact**. It is supplied at server start via `--bundles` and is never embedded in the package, the `server.json`, or any registry listing.

## Key documents
Expand Down
9 changes: 5 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "openadapt-agent"
version = "2.0.0.dev0"
description = "Agent-facing bridge for openadapt-flow: expose compiled workflow bundles as MCP servers and Agent Skills (Experimental)"
description = "Local agent bridge for governed OpenAdapt workflows, attended actions, MCP, and Agent Skills"
readme = "README.md"
# openadapt-flow pins <3.13 (rapidocr-onnxruntime); this package inherits it.
requires-python = ">=3.10,<3.13"
Expand All @@ -17,7 +17,7 @@ maintainers = [
{name = "OpenAdaptAI", email = "contact@openadapt.ai"}
]
classifiers = [
"Development Status :: 3 - Alpha",
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
Expand All @@ -32,16 +32,17 @@ keywords = ["mcp", "agent-skills", "gui", "automation", "workflow", "openadapt",
dependencies = [
# Governed workflow compiler/runtime this package bridges. Execution
# shells out to its CLI; bundle metadata loads via its IR.
"openadapt-flow>=1.9,<2",
"openadapt-flow>=1.18.1,<2",
# Official Model Context Protocol SDK (stdio server transport).
"mcp>=1.2",
"mcp>=1.28,<2",
# Structured concurrency runtime the mcp SDK already uses; we call
# anyio.run / to_thread directly.
"anyio>=4.0",
]

[project.optional-dependencies]
dev = [
"build>=1.2.0",
"pytest>=8.0.0",
"ruff>=0.4.0",
# Parses smithery.yaml in the distribution-artifact guard tests.
Expand Down
110 changes: 110 additions & 0 deletions scripts/check_dist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""Fail a release if its archives violate the OpenAdapt package boundary."""

from __future__ import annotations

import argparse
import tarfile
import zipfile
from pathlib import Path

_FORBIDDEN_PATH_MARKERS = (
"openimis",
"benchmark/openimis",
"benchmarks/openimis",
"adversary_corpus",
"identity_roc",
"pixel_verify_cert",
"oracle_recipe",
"real_emr",
"held_out_corpus",
"/private/",
)
_COPYLEFT_MARKERS = (
b"GNU " + b"AFFERO GENERAL PUBLIC LICENSE",
b"GNU " + b"GENERAL PUBLIC LICENSE",
b"SPDX-License-Identifier: " + b"AGPL-",
b"SPDX-License-Identifier: " + b"GPL-",
)


class ArtifactError(RuntimeError):
"""Distribution archive failed the release boundary."""


def _members(path: Path) -> dict[str, bytes]:
if path.suffix == ".whl":
with zipfile.ZipFile(path) as archive:
return {
info.filename: archive.read(info)
for info in archive.infolist()
if not info.is_dir()
}
if path.name.endswith(".tar.gz"):
with tarfile.open(path, "r:gz") as archive:
return {
member.name: extracted.read()
for member in archive.getmembers()
if member.isfile() and (extracted := archive.extractfile(member)) is not None
}
raise ArtifactError(f"unsupported artifact type: {path}")


def check(path: Path) -> None:
members = _members(path)
if not members:
raise ArtifactError(f"{path}: empty archive")

lowered_names = {name: name.lower() for name in members}
forbidden = sorted(
name
for name, lowered in lowered_names.items()
if any(marker in lowered for marker in _FORBIDDEN_PATH_MARKERS)
)
if forbidden:
raise ArtifactError(f"{path}: repository-only benchmark material in artifact: {forbidden}")

metadata = [
body
for name, body in members.items()
if name.endswith(".dist-info/METADATA") or name.endswith("/PKG-INFO")
]
if not metadata or not any(
b"License-Expression: MIT" in body or b"License: MIT" in body for body in metadata
):
raise ArtifactError(f"{path}: package metadata does not declare MIT")

license_files = [
body
for name, body in members.items()
if Path(name).name.upper() in {"LICENSE", "LICENSE.TXT", "LICENSE.MD"}
]
if not license_files or not any(
b"Permission is hereby granted, free of charge" in body for body in license_files
):
raise ArtifactError(f"{path}: MIT LICENSE is missing")

copied_copyleft = []
for name, body in members.items():
if name.endswith((".dist-info/METADATA", "/PKG-INFO")):
continue
body_upper = body.upper()
if any(marker.upper() in body_upper for marker in _COPYLEFT_MARKERS):
copied_copyleft.append(name)
copied_copyleft.sort()
if copied_copyleft:
raise ArtifactError(f"{path}: copied copyleft material in MIT artifact: {copied_copyleft}")


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("artifacts", nargs="+", type=Path)
args = parser.parse_args()
for artifact in args.artifacts:
check(artifact)
print(f"PASS {artifact}")
return 0


if __name__ == "__main__":
raise SystemExit(main())
21 changes: 14 additions & 7 deletions scripts/smoke_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@

python scripts/smoke_client.py --bundles /path/to/bundles [--allow-run]

Performs tools/list plus the read-only ``list_workflows`` and
``get_workflow`` calls over the stdio transport and prints the results.
It never invokes a run tool — executing a workflow is an operator action.
Performs tools/list plus ``list_workflows``, ``get_workflow``, and the
PHI-safe ``list_needs_attention`` call over stdio. It never invokes a
run or attended-action tool.
"""

from __future__ import annotations
Expand Down Expand Up @@ -50,11 +50,18 @@ async def main() -> int:
print("\nlist_workflows:")
print(json.dumps(payload, indent=2))

workflows = [w for w in payload["workflows"] if not w["load_error"]]
attention = await session.call_tool("list_needs_attention", {})
print("\nlist_needs_attention:")
print(attention.content[0].text)

workflows = [w for w in payload["workflows"] if w["available"]]
if workflows:
slug = workflows[0]["slug"]
detail = await session.call_tool("get_workflow", {"workflow": slug})
print(f"\nget_workflow({slug!r}):")
workflow_id = workflows[0]["id"]
detail = await session.call_tool(
"get_workflow",
{"workflow": workflow_id},
)
print(f"\nget_workflow({workflow_id!r}):")
print(detail.content[0].text)
return 0

Expand Down
4 changes: 2 additions & 2 deletions server.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.OpenAdaptAI/openadapt-agent",
"title": "OpenAdapt Agent (openadapt-flow bridge)",
"description": "Expose compiled openadapt-flow workflow bundles as governed MCP tools (Experimental).",
"description": "Local Beta bridge for governed openadapt-flow workflows and attended actions.",
"websiteUrl": "https://docs.openadapt.ai",
"repository": {
"url": "https://github.com/OpenAdaptAI/openadapt-agent",
Expand Down Expand Up @@ -47,7 +47,7 @@
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"notes": "Read-only by default. The server registers execution (run_*) tools only when the operator adds the --allow-run flag; a registry-launched install stays inspection-only unless that flag is appended. See docs/DISTRIBUTION.md."
"notes": "PHI-safe inspection and Needs Attention tools are available by default. Run tools require --allow-run. Continue, Skip, Teach, and Escalate require --allow-attended-actions; Continue and Skip additionally require a qualified deployment --config. See docs/DISTRIBUTION.md."
}
}
}
Loading
Loading