deer-flow/backend/docs/RUN_INTERACTION_POLICY.md
Evan 226d1bc5ce
fix(runtime): persist a rejected HITL call in the resume run's history (#8073)
* fix(runtime): persist a rejected HITL call in the resume run's history

An approval pause and its resolution are two Runs. The paused Run dispatches
the gated call; the resume Run produces its result. An approved call executes
and reaches on_tool_end, but a rejection is written by HITL after_model with no
tool callback. Journal reconciliation only persists such middleware-written
results for calls dispatched by the current Run, so the rejection stayed in
checkpoint state and never reached the run-event history: /messages and the
resume run's messages had no llm.tool.result for it.

The worker already reads the durable interrupt payload to recover the gated
tool names. It now also returns each paused tool_call_id with its name, and the
resume journal adopts those calls, so the existing reconciliation persists the
rejection under the resume Run. Approved results keep their tool-callback event
and are deduplicated by message id; results from other earlier Runs are still
not re-attributed.

* fix(runtime): keep HITL replay names independent of call adoption

Derive the replay-gate tool names and the adopted call ids separately
from the matched pause's actions, so an action without a usable
tool_call_id still gates replay. Pin that the all-reject escape paths
(approval disabled, gated tools changed or cleared while paused) reach
the history feed under the resume run, check resume-run attribution in
the mixed-decision test, and add a CHANGELOG entry.

* refactor(runtime): drop unused _pending_hitl_resume_calls wrapper

---------

Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
2026-11-10 15:11:09 +08:00

68 lines
3.9 KiB
Markdown

# Run Interaction Policy
The harness resolves a `RunInteractionPolicy` from the run context and uses it
as the single source for lead-agent tool visibility, clarification middleware
behavior, sandbox network approval eligibility, and system-prompt guidance.
The implementation lives in `../packages/harness/deerflow/agents/interaction_policy.py`.
Interactive runs inspect available context and proceed with reversible assumptions or independent work when appropriate. Ask when essential information, consequential choices, or authorization for high-risk work is missing; a fixed clarify-plan-act sequence is not required. Clarification interrupts and trust boundaries remain unchanged.
## Modes and precedence
Interactive runs may ask a human for clarification. Runs in `scheduled`,
`webhook`, or `autonomous` mode must use the available context, make minimal
reversible assumptions, list material assumptions, or return a structured
`BLOCKED` result naming the missing decision for high-risk or irreversible
work without sufficient authorization. They must never wait for a synchronous
human response. Suppressed clarification tool results must preserve this same
boundary, not tell the model to unconditionally carry out an ambiguous action.
An explicit `interaction_mode` takes precedence over legacy context hints.
An unknown explicit mode is a configuration error, never an interactive fallback.
Without an explicit mode, the resolver checks `non_interactive` (scheduled),
then `channel_name="github"` (webhook), then `disable_clarification`
(autonomous); otherwise it selects interactive. These legacy flags remain
supported while entry points migrate to explicit modes.
Sync and async sandbox network approval paths use the same resolved policy.
Unattended runs deny pending network requests without opening a Human Input
card. Subagents always deny pending requests, even in an interactive run.
## Gateway trust boundary
`interaction_mode`, `non_interactive`, `disable_clarification`, and
`channel_name` are internal-caller-only Gateway context keys. The latter two
remain runtime-only and are never copied into checkpoint-persisted
`configurable`. Public run requests cannot use these keys to disable
clarification, impersonate a webhook channel, or override a scheduled run back
to interactive behavior.
## HITL tool approval
`hitl_approval` is an operator-controlled, opt-in gate around selected tools,
not a way for a non-interactive run to wait for clarification. Interactive
Web/SDK runs pause through LangGraph `interrupt()` and enter
`waiting_for_input`; only a matching approval response can resume the current
checkpoint. Scheduled, webhook, autonomous, IM, extension, and MCP notification
runs follow `hitl_approval.non_interactive`. Subagents and graphs without resume
support always fail closed, including when that setting is `allow`.
Resume consumption is a store-level compare-and-swap bound to the validated
action `request_id` mapping; the middleware recomputes those IDs from the live
AI message before applying any decision. If worker attachment fails after
consumption, the Gateway conditionally restores that exact answered run to
`waiting_for_input` so a valid response or all-reject can retry safely.
The resume run's journal adopts the paused calls it answers, recovered from the
durable interrupt payload. Approved calls reach the history feed through their
tool callback; rejections have none, so the journal reconciles them under the
resume run like other middleware-written results (#4666). Results from other
earlier runs are not re-attributed.
`waiting_for_input` is not a terminal success or failure. Goal checks, final
artifact delivery checks, and batch continuation cancellation must not treat a
pending approval as a completed source run. Pending approvals do not expire and
block new runs on the same thread until resolved. See
[HITL Tool Approval](CONFIGURATION.md#hitl-tool-approval) for configuration and
argument-summary visibility limits.