Skip to content
Open
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 openadapt_flow/console/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,17 +607,24 @@ def execute_attention_action(
"the attention item is no longer current; reload the queue"
)
request = human_decisions.admit_console_action(run_dir, item, payload)
# A person answered on the console or the paired phone: the
# operator identity is server-derived from the local OS session and
# the request arrived through a browser. Asserted explicitly rather
# than left to default, because `unknown` is reserved for a caller
# that genuinely did not say.
decision = (
attended_service.execute(
run_dir,
request,
operator=operator,
decided_by="human",
)
if attended_service is not None
else execute_attended_action(
run_dir,
request,
operator=operator,
decided_by="human",
)
)
except (AttendedActionRefused, ResumeRefused) as exc:
Expand Down
4 changes: 4 additions & 0 deletions openadapt_flow/console/human_decisions.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,10 +640,14 @@ def execute_remote_attended_action(
deployment=deployment,
principal=principal,
)
# A remote person answered. The relay admits a decision only against an
# authenticated principal whose assurance the control plane raised to AAL2,
# so this path cannot carry a model's answer.
return execute_attended_action(
run_dir,
engine_request,
operator=principal.subject,
decided_by="human",
executor=executor,
key=key,
)
Expand Down
44 changes: 43 additions & 1 deletion openadapt_flow/runtime/durable/attended.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,36 @@ class AttendedDecision(BaseModel):
idempotency_key: str
action: Literal["continue", "skip", "reject", "teach", "escalate"]
operator: str
#: WHAT KIND of decider produced this answer, as distinct from ``operator``,
#: which records WHICH IDENTITY it was attributed to. These are different
#: questions and only one of them was answerable before this field.
#:
#: ``operator`` is the local OS account or the authenticated remote
#: subject. The MCP bridge in ``openadapt-agent`` derives it from the same
#: ``_local_operator_identity()`` a person's own console does, so a
#: decision a MODEL submitted and one a PERSON submitted from the same
#: machine carried an IDENTICAL ``operator`` string. Any rate computed over
#: this journal therefore mixed the two populations with no way to separate
#: them afterwards.
#:
#: That matters most for the exact measurement the attended vocabulary
#: exists to produce. An agreement rate is a claim about what PEOPLE
#: concluded when they read the evidence. A model's answers are a different
#: population, and averaging them together answers neither question.
#:
#: Caller-asserted, exactly like ``operator``, and deliberately NOT a field
#: on :class:`AttendedActionRequest`: the request is what a relay or an MCP
#: client sends and ``request_digest`` commits to it, so a submitter must
#: never be able to describe its own nature. The trusted caller that
#: already asserts ``operator`` asserts this beside it.
#:
#: The default is ``unknown``, and that is deliberate. A caller that does
#: not declare is EXCLUDED from a human rate rather than silently counted
#: into it, and every decision written before this field existed reads back
#: as ``unknown`` -- which is the truth, because the bridge predates it.
#: Defaulting to ``human`` would have re-created the same bias one level
#: down. A closed enum, never free text; a new member is additive.
decided_by: Literal["human", "automation", "unknown"] = "unknown"
disposition: Optional[str] = None
status: Literal[
"prepared",
Expand Down Expand Up @@ -1171,11 +1201,18 @@ def execute_attended_action(
request: AttendedActionRequest,
*,
operator: str,
decided_by: Literal["human", "automation", "unknown"] = "unknown",
executor: Optional[AttendedActionExecutor] = None,
key: Optional[str] = None,
now: Optional[datetime] = None,
) -> AttendedDecision:
"""Admit and execute one attended decision under exact binding."""
"""Admit and execute one attended decision under exact binding.

``decided_by`` records WHAT KIND of decider this is, beside ``operator``,
which records WHICH identity. See :class:`AttendedDecision` for why the two
are separate questions and why the default is ``unknown`` rather than
``human``.
"""
from openadapt_flow import crypto as _crypto

key = _crypto.resolve_key(key)
Expand Down Expand Up @@ -1269,6 +1306,7 @@ def execute_attended_action(
idempotency_key=request.idempotency_key,
action=request.action,
operator=operator,
decided_by=decided_by,
disposition=request.disposition or "rejected_by_operator",
status="rejected",
message=(
Expand All @@ -1293,6 +1331,7 @@ def execute_attended_action(
idempotency_key=request.idempotency_key,
action=request.action,
operator=operator,
decided_by=decided_by,
disposition=request.disposition or "teach_requested",
status="needs_demonstration",
message=(
Expand All @@ -1314,6 +1353,7 @@ def execute_attended_action(
idempotency_key=request.idempotency_key,
action=request.action,
operator=operator,
decided_by=decided_by,
disposition=request.disposition or "needs_assistance",
status="escalated",
message=(
Expand Down Expand Up @@ -1346,6 +1386,7 @@ def execute_attended_action(
idempotency_key=request.idempotency_key,
action=request.action,
operator=operator,
decided_by=decided_by,
disposition=request.disposition,
status="prepared",
message="request admitted; no delivery attempted",
Expand Down Expand Up @@ -1392,6 +1433,7 @@ def execute_attended_action(
idempotency_key=request.idempotency_key,
action=request.action,
operator=operator,
decided_by=decided_by,
disposition=request.disposition,
status=result.status,
message=result.message,
Expand Down
10 changes: 9 additions & 1 deletion openadapt_flow/runtime/durable/attended_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,14 +353,22 @@ def execute(
request: AttendedActionRequest,
*,
operator: str,
decided_by: Literal["human", "automation", "unknown"] = "unknown",
) -> AttendedDecision:
"""Admit and execute one exact attended decision on the live session."""
"""Admit and execute one exact attended decision on the live session.

``decided_by`` is relayed unchanged. This service owns the live
executor, not the question of who decided, and inventing an answer here
would attribute a decision on behalf of a caller that never made the
claim.
"""
if not self._entered:
raise RuntimeError("attended action service is not open")
return execute_attended_action(
run_dir,
request,
operator=operator,
decided_by=decided_by,
executor=self._owner,
key=self._key,
)
Expand Down
3 changes: 2 additions & 1 deletion tests/test_attended_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1882,11 +1882,12 @@ def test_attended_http_action_requires_auth_csrf_and_exact_capability(
executor = _ResultExecutor()

class Service:
def execute(self, run_dir, request, *, operator):
def execute(self, run_dir, request, *, operator, decided_by="unknown"):
return execute_attended_action(
run_dir,
request,
operator=operator,
decided_by=decided_by,
executor=executor,
)

Expand Down
23 changes: 15 additions & 8 deletions tests/test_console.py
Original file line number Diff line number Diff line change
Expand Up @@ -1768,10 +1768,12 @@ def skip_run(self, _run_dir, _capability, _approval):
)
monkeypatch.setattr(
"openadapt_flow.runtime.durable.attended_service.execute_attended_action",
lambda _run_dir, _request, *, operator, executor, key: executor.continue_run(
Path("run"),
SimpleNamespace(step_id=f"step-for-{operator}"),
SimpleNamespace(),
lambda _run_dir, _request, *, operator, executor, key, decided_by="unknown": (
executor.continue_run(
Path("run"),
SimpleNamespace(step_id=f"step-for-{operator}"),
SimpleNamespace(),
)
),
)

Expand Down Expand Up @@ -1927,10 +1929,12 @@ def fake_configured(_backend, **_kwargs):
)
monkeypatch.setattr(
"openadapt_flow.runtime.durable.attended_service.execute_attended_action",
lambda _run_dir, _request, *, operator, executor, key: executor.continue_run(
Path("run"),
SimpleNamespace(step_id=f"step-for-{operator}"),
SimpleNamespace(),
lambda _run_dir, _request, *, operator, executor, key, decided_by="unknown": (
executor.continue_run(
Path("run"),
SimpleNamespace(step_id=f"step-for-{operator}"),
SimpleNamespace(),
)
),
)

Expand Down Expand Up @@ -2260,8 +2264,11 @@ def test_public_attended_action_service_contract_is_stable():
"run_dir",
"request",
"operator",
"decided_by",
]
assert execute.parameters["operator"].kind is inspect.Parameter.KEYWORD_ONLY
assert execute.parameters["decided_by"].kind is inspect.Parameter.KEYWORD_ONLY
assert execute.parameters["decided_by"].default == "unknown"

service = AttendedActionService(DeploymentConfig())
assert not hasattr(service, "continue_run")
Expand Down
Loading
Loading