Jump to content

Connect SuperML | Leeroopedia MCP: Equip your AI agents with best practices, code verification, and debugging knowledge. Powered by Leeroo — building Organizational Superintelligence. Contact us at founders@leeroo.com.

Principle:Microsoft Agent framework Human in the Loop Request

From Leeroopedia


Template:Microsoft Agent framework Sidebar

Overview

Human-in-the-Loop Request is the principle governing how a workflow executor pauses its execution to request information from an external system or human operator. An executor calls ctx.request_info(request_data, response_type) to emit a request_info WorkflowEvent and suspend processing. The workflow transitions into the IDLE_WITH_PENDING_REQUESTS state and remains there until the requested response is provided externally, at which point execution resumes from the point of suspension.

Property Value
Principle Name Human-in-the-Loop Request
SDK Microsoft Agent Framework
Repository Microsoft Agent Framework
Source Reference python/packages/core/agent_framework/_workflows/_workflow_context.py:L367-398
API WorkflowContext.request_info(request_data, response_type, *, request_id)
Import Access via WorkflowContext parameter in @handler methods
Domains Workflow_Engine, Human_in_the_Loop

Description

Within the Microsoft Agent Framework's workflow engine, executors sometimes cannot proceed without information that is not available inside the workflow itself. This information might come from a human operator (e.g., confirming a decision, supplying credentials, or providing clarification) or from an external system (e.g., a third-party API that requires out-of-band authorization). The Human-in-the-Loop Request principle formalizes this pause-and-resume pattern as a first-class workflow primitive.

When an executor determines that it needs external input, it calls ctx.request_info(request_data, response_type) on its WorkflowContext. This call performs two actions:

  1. It constructs a WorkflowEvent of type "request_info", carrying the request_data payload, a request_id (either caller-supplied or auto-generated as a UUID), and the expected response_type.
  2. It delegates to RunnerContext.add_request_info_event(), which registers the event as a pending request and emits it into the event stream.

Once emitted, the workflow engine detects that there are unresolved pending requests and transitions the workflow state to IDLE_WITH_PENDING_REQUESTS. In this state, the workflow does not schedule further supersteps or executor invocations. It remains idle until an external caller provides responses via Workflow.run(responses={"request_id": response_value}).

Theoretical Basis

Pause-and-Resume Semantics

The request_info mechanism implements a cooperative suspension model. Unlike preemptive interruption, the executor explicitly chooses to yield control by calling ctx.request_info(). This makes the suspension point deterministic and auditable: the caller knows exactly which executor paused, what data it requested, and what response type it expects.

This design follows the continuation-passing pattern: the workflow's state (including the pending request registry) serves as the continuation, and the external response acts as the value that resumes the continuation. The framework persists this state in checkpoints, enabling durable workflows that survive process restarts.

State Machine Transition

The workflow state machine includes two request-related states:

State Description
IN_PROGRESS_PENDING_REQUESTS The workflow is still executing (other executors may be active), but at least one request_info event has been emitted and is awaiting a response.
IDLE_WITH_PENDING_REQUESTS All executors have completed their current processing. The workflow has stopped and is waiting exclusively for external responses to pending requests before it can resume.

The transition from IN_PROGRESS_PENDING_REQUESTS to IDLE_WITH_PENDING_REQUESTS occurs when the last active executor finishes its handler and there are still unresolved requests. When responses arrive, the framework matches them by request_id to the originating executor and dispatches the response through the executor's @response_handler.

Request Validation

Before emitting the event, request_info checks whether the executor has a @response_handler defined for the given (request_type, response_type) pair. If no matching handler exists, a warning is logged advising the developer to define one. The request is still emitted (it is not silently dropped), but any response that arrives will have no handler to process it.

This validation-with-warning approach follows the fail-open with diagnostics principle: it avoids hard failures in development while providing clear guidance about misconfiguration.

Request ID Correlation

Each request carries a request_id that uniquely identifies it within the workflow run. If the caller does not supply one, a UUID is auto-generated. This ID is used for three purposes:

  • Event correlation: The WorkflowEvent carries the request_id, enabling external systems to match requests to responses.
  • Runner context tracking: The RunnerContext stores pending requests in a _pending_request_info_events dictionary keyed by request_id.
  • Checkpoint restoration: When a workflow is resumed from a checkpoint, responses can be supplied by request_id, ensuring they reach the correct executor.

Usage

Basic Request

from agent_framework import Executor, handler, WorkflowContext

class ApprovalExecutor(Executor):
    @handler
    async def need_approval(self, msg: str, ctx: WorkflowContext) -> None:
        await ctx.request_info(
            request_data="Please approve this action",
            response_type=bool,
            request_id="approval_request",
        )

Streaming Detection

from agent_framework import Workflow

workflow = Workflow(...)

async for event in workflow.run("start", stream=True):
    if event.type == "request_info":
        print(f"Request from: {event.source_executor_id}")
        print(f"Request ID: {event.request_id}")
        print(f"Data: {event.data}")

Supplying Responses

# Resume the workflow by providing the response keyed by request_id
result = await workflow.run(
    responses={"approval_request": True},
)

I/O Contract

Inputs

Parameter Type Default Description
request_data object (required) The payload describing what information is being requested. This is embedded in the emitted WorkflowEvent as event.data.
response_type type (required) The expected Python type of the response. Used to validate the response and to match against @response_handler signatures on the executor.
request_id None None Optional unique identifier for the request. If not provided, a UUID is auto-generated. Used for correlating responses back to this specific request.

Outputs

Output Type Description
Return value None The method returns None. Its effect is the emission of a request_info WorkflowEvent into the event stream and the registration of the request as pending in the RunnerContext.
Side effect: event WorkflowEvent (type="request_info") A workflow event carrying request_id, source_executor_id, request_data, request_type, and response_type.
Side effect: state IDLE_WITH_PENDING_REQUESTS After all active executors finish, the workflow transitions to this state if unresolved requests remain.

Related Pages

Template:Sources

Page Connections

Double-click a node to navigate. Hold to expand connections.
Principle
Implementation
Heuristic
Environment