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
23 changes: 21 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,33 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
include:
# Cover both dependency edges without doubling the hosted matrix.
- python-version: "3.10"
dependency-set: floor
- python-version: "3.11"
dependency-set: current
- python-version: "3.12"
dependency-set: current
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install
- uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
if: matrix.dependency-set == 'floor'
- name: Verify lock consistency
if: matrix.dependency-set == 'floor'
run: uv lock --locked
- name: Install dependency floors
if: matrix.dependency-set == 'floor'
run: >-
pip install -e ".[dev]"
"openadapt-flow==1.26.0"
"mcp==1.28.0"
- name: Install current allowed dependencies
if: matrix.dependency-set == 'current'
run: pip install -e ".[dev]"
- name: Lint
run: ruff check src tests scripts
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Attended actions are separate, exact tools:
| --- | --- |
| `continue_attention` | The operator confirms they completed the paused task in the live app. Flow revalidates its postconditions and independent effects, checkpoints it as human-completed, and resumes after it. It does not perform the completed action again. |
| `skip_attention` | Flow applies only an already-declared, non-consequential skip. A stale, undeclared, consequential, or ambiguous skip is refused. |
| `reject_attention` | Terminates this run and dispatches no new action. Earlier run actions can still have effects, so review the protected local report and transaction outcome. Use Escalate if a qualified operator can still continue the run. |
| `teach_attention` | Records an audited request for a corrective demonstration. Flow's existing revision and regression gates decide what can be promoted. |
| `escalate_attention` | Records an audited escalation and leaves the exact durable pause intact for a qualified operator. |

Expand All @@ -136,7 +137,7 @@ confirmation signal, not cryptographic proof that a particular person
clicked it or proof of that person's identity. Flow separately records
the effective local OS account as the operator. Clients without form
elicitation cannot execute attended actions through this MCP bridge; the same
Continue, Skip, Teach, and Escalate capabilities remain available through
Continue, Skip, Reject, Teach, and Escalate capabilities remain available through
Flow's attended console/CLI. MCP destructive/idempotent/open-world
annotations give the host an additional approval signal. Neither those
hints nor the elicitation replaces Flow's signed capability, live
Expand All @@ -161,7 +162,7 @@ Retries with the same idempotency key return the prior terminal decision
instead of repeating it.

With `--allow-attended-actions` but no deployment `--config`, the safe
Teach and Escalate transitions remain available; Continue and Skip are
Reject, Teach, and Escalate transitions remain available; Continue and Skip are
not registered until Flow can construct the deployment-bound live
verifier and backend.

Expand All @@ -175,7 +176,7 @@ verifier and backend.
| `list_needs_attention` | Always |
| `get_attention_item` | Always |
| `run_workflow_<opaque-id>` | `--allow-run` |
| `teach_attention`, `escalate_attention` | `--allow-attended-actions` |
| `reject_attention`, `teach_attention`, `escalate_attention` | `--allow-attended-actions` |
| `continue_attention`, `skip_attention` | `--allow-attended-actions` plus a qualified deployment `--config` |

## Run outcomes
Expand Down
18 changes: 14 additions & 4 deletions docs/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ operation:
use demonstrated values. It requires run authority and is only for
synthetic demos; it never places those values in a tool schema.

`--allow-attended-actions` registers Teach and Escalate. Continue and
`--allow-attended-actions` registers Reject, Teach, and Escalate. Continue and
Skip are registered only when a deployment configuration lets Flow
construct its bound live executor.

Expand Down Expand Up @@ -160,9 +160,11 @@ person clicked, nor identity proof. Flow separately records the effective
local OS account as the operator. A client that does not advertise
form elicitation cannot execute attended mutations through MCP; the
operator uses Flow's existing attended console/CLI instead, where all
four capabilities remain available. This is a transport authorization
five capabilities remain available. This is a transport authorization
choice, not a read-only conversion. Tool annotations also mark Continue
and Skip as destructive, idempotent, and open-world so the host can apply
and Skip as destructive, idempotent, and open-world. Reject is destructive
and idempotent but not open-world because it dispatches no new application
action. These hints let the host apply
its own approval policy. Annotations and elicitation do not replace
Flow's signed capability, live revalidation, idempotency, or durable
audit.
Expand Down Expand Up @@ -190,6 +192,14 @@ and compiled workflow declare a safe, non-consequential skip. Flow
rechecks that guard against current state. Consequential, stale,
ambiguous, or undeclared skips are refused.

### Reject

Reject terminates the current run and permanently prevents resume. The
rejection dispatches no new application action. Earlier steps in the run can
still have effects, so the operator must inspect the protected local report
and transaction outcome. Use Escalate instead when a qualified operator can
still inspect and continue the run.

### Teach

Teach records an audited request for a corrective demonstration. The
Expand Down Expand Up @@ -262,7 +272,7 @@ Tests cover:
- PHI-safe queue projections and path traversal refusal;
- stale capability, unknown field, and false-confirmation refusal;
- idempotent Continue without re-actuation;
- Teach and Escalate without a live service;
- Reject, Teach, and Escalate without a live service;
- delegation to Flow's public service context;
- compatibility with Flow's public, thread-owned attended service;
- success/halt/refusal/timeout outcome mapping;
Expand Down
6 changes: 3 additions & 3 deletions docs/DISTRIBUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ 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
adds Teach and Escalate, while a qualified deployment config also makes
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 four capabilities remain available. This
attended console/CLI, where all five capabilities remain available. This
matches the security model in [`DESIGN.md`](DESIGN.md).

> There is intentionally **no** hosted, multi-tenant "official OpenAdapt
Expand Down Expand Up @@ -68,7 +68,7 @@ and [`../manifest.json`](../manifest.json).
- `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.
- `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

## 3. Release automation vs. founder one-time actions
Expand Down
3 changes: 2 additions & 1 deletion llms.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
## What it provides

- `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.
- `--allow-attended-actions` adds exact Reject, 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.
- `reject_attention`: terminates this run and dispatches no new action. Earlier run actions may have effects, so review the protected local report and transaction outcome. Use escalation when a qualified operator can still continue the run.
- `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.

Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
"allow_attended_actions": {
"type": "boolean",
"title": "Allow Needs Attention decisions",
"description": "Add Teach and Escalate. A qualified deployment config also enables Continue and Skip with live revalidation.",
"description": "Add Reject, Teach, and Escalate. Reject dispatches no new action, but earlier run effects still require protected local review. A qualified deployment config also enables Continue and Skip with live revalidation.",
"required": false,
"default": false
},
Expand Down
7 changes: 6 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ 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.18.1,<2",
# FLOOR IS LOAD-BEARING. `reject`, its `rejected_by_operator` disposition,
# and the `decided_by` provenance keyword are named symbols that only exist
# from 1.26.0. A resolver satisfied by 1.25.x fails at CALL time, inside the
# attended decision path, with a TypeError rather than at resolve. Raise
# this with every new symbol taken from Flow's attended contract.
"openadapt-flow>=1.26.0,<2",
# Official Model Context Protocol SDK (stdio server transport).
"mcp>=1.28,<2",
# Structured concurrency runtime the mcp SDK already uses; we call
Expand Down
2 changes: 1 addition & 1 deletion server.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
],
"_meta": {
"io.modelcontextprotocol.registry/publisher-provided": {
"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."
"notes": "PHI-safe inspection and Needs Attention tools are available by default. Run tools require --allow-run. Reject, Teach, and Escalate require --allow-attended-actions; Continue and Skip also require --allow-attended-actions and a qualified deployment --config. Reject dispatches no new action, but earlier run actions may have effects and require review of the protected local outcome. See docs/DISTRIBUTION.md."
}
}
}
50 changes: 49 additions & 1 deletion src/openadapt_agent/attended.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@
"The escalation was recorded and the durable pause remains available "
"for qualified assistance."
),
# Deliberately not worded like the escalation above. That one says the
# pause remains; this one says the run is over. A caller told the wrong
# one of those acts on it.
"rejected": (
"The rejection was recorded and the run is terminal. This rejection "
"dispatched no new action. Earlier run actions may have effects; review "
"the protected local report and transaction outcome. No approval can "
"resume the run. The durable pause is retained only as the audit record "
"of what was rejected."
),
}


Expand Down Expand Up @@ -130,6 +140,24 @@ class AttendedTool:
"pause for a qualified operator."
),
),
"reject_attention": AttendedTool(
action="reject",
confirmation="confirm_run_must_not_proceed",
disposition="rejected_by_operator",
description=(
"End this run because it must not proceed. Use it only after "
"reading the live application and concluding OpenAdapt was RIGHT "
"to stop. This TERMINATES the run: no approval resumes it, and "
"the durable pause is kept only as the audit record. It is not "
"escalate_attention, which parks the run for a colleague who can "
"still continue it, and it is not teach_attention, which changes "
"future runs. It asserts nothing about the saved workflow and "
"dispatches no new action. Earlier run actions can still have "
"effects, so inspect the protected report and transaction outcome. "
"Flow independently refuses a rejection whose delivery may already "
"have landed."
),
),
}


Expand Down Expand Up @@ -206,9 +234,22 @@ def live_actions_ready(self) -> bool:
return self.allow_actions and self.service is not None

def enabled_action_tools(self) -> tuple[str, ...]:
"""Which action tools this bridge exposes, and why reject is not gated.

``reject_attention`` sits with teach and escalate rather than behind
``live_actions_ready``. That gate exists because continue and skip need
Flow's deployment-bound live executor to re-read the application and
act on it. Reject dispatches no new action and resumes nothing, so it
has nothing to gate on -- the same reason Flow's own ``_allowed_actions``
offers it at a pause carrying no resolvable action step at all.

Withholding it would also leave this bridge able to say "proceed" --
which writes to the system of record -- while unable to say "stop".
The only thing removed by that configuration is the brake.
"""
if not self.allow_actions:
return ()
tools = ["teach_attention", "escalate_attention"]
tools = ["reject_attention", "teach_attention", "escalate_attention"]
if self.live_actions_ready:
tools[0:0] = ["continue_attention", "skip_attention"]
return tuple(tools)
Expand Down Expand Up @@ -327,17 +368,24 @@ def act(self, tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]:
disposition=tool.disposition,
)
try:
# This bridge submits on behalf of a MODEL, and it derives
# `operator` from the same local OS identity a person's own console
# uses -- so the identity alone cannot tell the two apart. Declared
# here, at the one place that knows, so an agreement rate computed
# over Flow's journal can filter to decisions people actually made.
if self.service is not None:
decision = self.service.execute(
path,
request,
operator=self.operator,
decided_by="automation",
)
else:
decision = execute_attended_action(
path,
request,
operator=self.operator,
decided_by="automation",
)
except (ApprovalRequired, AttendedActionRefused, ResumeRefused) as exc:
_LOG.info(
Expand Down
2 changes: 1 addition & 1 deletion src/openadapt_agent/bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ def list_tool_specs(self) -> list[ToolSpec]:
input_schema=action_input_schema(tool),
annotations={
"readOnlyHint": False,
"destructiveHint": tool.action in {"continue", "skip"},
"destructiveHint": tool.action in {"continue", "skip", "reject"},
"idempotentHint": True,
"openWorldHint": tool.action in {"continue", "skip"},
},
Expand Down
2 changes: 1 addition & 1 deletion src/openadapt_agent/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def build_parser() -> argparse.ArgumentParser:
"--allow-attended-actions",
action="store_true",
help=(
"Register governed Teach/Escalate tools for signed durable pauses. "
"Register governed Reject/Teach/Escalate tools for signed durable pauses. "
"With --config, also register Continue/Skip through Flow's "
"deployment-bound live verifier and deterministic resume path."
),
Expand Down
9 changes: 9 additions & 0 deletions src/openadapt_agent/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@
"Confirm that OpenAdapt should record an audited escalation and preserve "
"the exact durable pause for qualified assistance."
),
"reject_attention": (
"Confirm that OpenAdapt must end this run without resuming it. This rejection "
"dispatches no new action, but earlier run actions may have effects. Review the "
"protected local report and transaction outcome. The durable pause remains only "
"as the audit record. Use escalation instead when a qualified colleague must "
"inspect and possibly continue the run."
),
}


Expand Down Expand Up @@ -101,6 +108,8 @@ def build_server(bridge: AgentBridge) -> Server:
"plus protocol-native operator elicitation, an exact signed "
"capability, live revalidation, and a stable idempotency key; they "
"never re-actuate the human-completed step. "
"Reject terminates the run and dispatches no new action, but earlier "
"run effects still require review of the protected local outcome. "
"Never report a halted, refused, or timed-out run as a success."
),
)
Expand Down
4 changes: 4 additions & 0 deletions src/openadapt_agent/skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@
the paused task in the live application. Flow revalidates the outcome
and resumes after it; it never performs that completed action again.
- `skip_attention` only for an allowed, declared skip.
- `reject_attention` to terminate this run without dispatching a new
action. Earlier run actions may have effects. Review the protected local
report and transaction outcome. Use escalation if a qualified operator
can still continue the run.
- `teach_attention` to request a corrective demonstration.
- `escalate_attention` to preserve the pause for qualified assistance.

Expand Down
4 changes: 4 additions & 0 deletions tests/golden/skill_appendix.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ that matches their explicit decision:
the paused task in the live application. Flow revalidates the outcome
and resumes after it; it never performs that completed action again.
- `skip_attention` only for an allowed, declared skip.
- `reject_attention` to terminate this run without dispatching a new
action. Earlier run actions may have effects. Review the protected local
report and transaction outcome. Use escalation if a qualified operator
can still continue the run.
- `teach_attention` to request a corrective demonstration.
- `escalate_attention` to preserve the pause for qualified assistance.

Expand Down
Loading