Skip to content

docs: document the attended-decision path and its ingress requirement - #86

Merged
abrichr merged 1 commit into
mainfrom
docs/attended-decision-path
Jul 28, 2026
Merged

docs: document the attended-decision path and its ingress requirement#86
abrichr merged 1 commit into
mainfrom
docs/attended-decision-path

Conversation

@abrichr

@abrichr abrichr commented Jul 28, 2026

Copy link
Copy Markdown
Member

What

When a run cannot confirm something it halts and puts a bounded question in front of a person, reachable from a phone. That path shipped across four repos — openadapt-types #12/#13, openadapt-flow #289/#290, openadapt-desktop #75/#78/#79/#80/#82, openadapt-cloud #175 — and docs.openadapt.ai said nothing about it beyond auto-generated changelog lines.

This extends the existing concepts/halt-learn-loop.md rather than adding a page. The phone's "teach the correction" answer is the front door to the learn loop already documented there, so the two halves now read as one page: where a halt goes, then what happens when someone teaches the fix.

Established from origin/main, not from a summary

Every property below was verified by reading origin/main of the four repos (via git show origin/main:…, since several checkouts are on feature branches):

  • The phone returns a signed decision, not an execution result. continue_run calls revalidate_attended_completion against a fresh observation and returns refused when it fails — before any resume.
  • The operator's own step is verified, not replayed. checkpoint_human_completed_step — "advance a linear resume point after outcome verification, without acting" — records actuation="human_attended" and refuses if postconditions or the independent effect check did not pass.
  • Evidence stays on the runner. portable_remote_decision_task discards the local presentation block; the hosted envelope has no free-text field anywhere, enforced by exact-key checks in both TypeScript and SQL.
  • Reverse-QR pairing, loopback-only ingress, and the fail-closed rules in engine/portal/ingress.py.

One correction to the brief I was given

The brief said the hosted envelope carries safe_slots null. It does not — safe_slots is a typed object of three nullable bounded integers, individually null per task. What Cloud actually loses is the entire local presentation block. The docs state the accurate version. The conclusion the brief drew is still right, and for a stronger reason: the hosted surface shows less because values and prose have no field to travel in, not because a field was nulled.

The ingress requirement

Deliberately not a footnote. The portal is loopback-only by default and a phone cannot reach it until the customer terminates trusted TLS themselves. A reader who installs and only then discovers this has been misled, so it appears in three places they hit before trying:

  1. A !!! warning in the concept page, plus the constraint stated in the section's opening sentence so a skimmer sees it too.
  2. reference/configuration.md — a new The mobile decision portal section with the exact variables and every fail-closed rule.
  3. guides/deploy-on-prem.md — a "plan the ingress before you pilot the phone path" callout in the deployment-planning flow.

Framed as what it is: we did not open a listening surface on someone else's network for our convenience. The runner is published under the customer's certificate, access policy, and logging, so the phone path inherits controls their security team already audits. In a governance sale that is a strength, not an apology.

Files

File Change
concepts/halt-learn-loop.md New "Where a halt goes" arc; retitled; closing rationale now covers both halves
reference/configuration.md New portal settings section + fail-closed rules
guides/deploy-on-prem.md Ingress planning section + two rows in the boundary table
guides/security-and-data-handling.md "When a halt is answered by a person" under the data-flow model
concepts/durable-runtime.md, concepts/identity-gate.md Cross-links — both end at a halt and previously said nothing about where it goes
mkdocs.yml Nav label follows the retitled page; filename and URL unchanged

Deliberately not included

  • No screenshot. I vetted .private/mobile_demo/shots/2026-07-27-hf-review-after-02-decision-viewport.png and it is a synthetic fixture — a "button labelled Open", no MockMed data, no identifiers, no real application. But copying out of .private/ into a public repo was blocked by the workspace guardrail, and that guardrail exists to force exactly this decision to a human. I did not work around it. Adding it later is a one-line follow-up if wanted.
  • No marketing-site change. Reasoning in the session report; short version is that this is not reachable from a phone without customer-configured TLS ingress, so putting it on openadapt.ai would imply a self-serve availability that does not exist. pages/dental.js already promises a "ready-to-finish queue", which remains accurate and unchanged.
  • No version-specific caveats. Noted separately for follow-up: the richest halt narration (halt_detail) is on Flow main but not in released v1.25.1, and Desktop pins openadapt-flow==1.25.0 / openadapt-types==0.6.1. The documented loop works on the pinned versions via the compat mapping Desktop already ships; only the narration fidelity differs. That skew is transient and would go stale in the docs within days.

Verification

  • scripts/validate_docs.py — passed
  • pytest — 101 passed
  • mkdocs build --strict — clean; all three new cross-page anchors confirmed present in the built HTML

🤖 Generated with Claude Code

https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM

A run that cannot confirm something halts and puts a bounded question in
front of a person, reachable from a phone. That path shipped across
openadapt-types, openadapt-flow, openadapt-desktop, and openadapt-cloud,
and the docs said nothing about it beyond auto-generated changelog lines.

Extends the existing halt-learn-loop page rather than adding a new one:
the phone's "teach the correction" answer is literally the front door to
the learn loop already documented there, so the two halves now read as
one page — where a halt goes, and what happens when someone teaches the
fix. Nav label and closing rationale updated to match.

The properties that distinguish this path, all established from
origin/main rather than from a summary:

- The phone returns a signed decision, not an execution result. The
  engine re-reads live state and re-verifies before continuing, and
  refuses when the application has not actually changed. An operator's
  answer never marks a run verified.
- The operator's own completed step is verified, not replayed.
- Full evidence stays on the customer-controlled runner. The hosted
  envelope carries no free-text field at all, so a hosted dashboard
  shows less than the phone by design — documented as the feature it is
  rather than hidden as a limitation.
- Pairing is a one-use QR whose confirmation code is minted on the
  claiming phone and typed back on the runner. The reversal is what
  makes it phishing-resistant, so the reasoning is written down.

The ingress requirement is deliberately not a footnote. The portal is
loopback-only by default and a phone cannot reach it until the customer
terminates trusted TLS themselves. A reader who installs and then
discovers that has been misled, so it appears in three places they hit
before trying: a warning in the concept page, the exact variables and
fail-closed rules in the configuration reference, and a plan-for-this
callout in the on-prem guide. It is framed as what it is — we did not
open a listening surface on someone else's network for our convenience,
and in a governance sale that is a strength.

Also cross-links from the durable-runtime handover and the identity
gate, which both end at a halt and previously said nothing about where
it goes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NyCHrzA1psrKMFfroYbzaM
@abrichr
abrichr merged commit 847a3e5 into main Jul 28, 2026
5 checks passed
@abrichr
abrichr deleted the docs/attended-decision-path branch July 28, 2026 01:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant