AI disclosure: This issue was drafted and analyzed with Codex using GPT-5.4. I have reviewed the analysis, and I am responsible for the content.
Summary
This issue tracks the graphon side of a Human-in-the-Loop (HITL) boundary cleanup.
The goal is to make graphon own only the Human Input control-flow state machine, while the host application owns form schemas, session persistence, delivery, submission restoration, and other product-facing HITL semantics.
This issue is intentionally limited to graphon. Host-side changes will be handled separately.
Problem
Today, HumanInputNode still depends on host-facing runtime and form abstractions, and PauseReason still carries host-oriented payload.
That has a few practical costs:
graphon needs to know about form creation/loading semantics.
graphon pause payloads include form content, input schema, action schema, and resolved defaults.
graphon restores submitted payloads and owns behavior that should live in the host.
- HITL changes tend to require synchronized edits in both
graphon and the embedding application.
This makes the boundary too large and causes host product semantics to leak back into graphon.
Proposed boundary
The core protocol should be a decision callback:
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from graphon.runtime.graph_runtime_state_protocol import ReadOnlyVariablePool
from graphon.variables.segments import Segment
@dataclass(frozen=True)
class HITLContext:
workflow_execution_id: str
node_id: str
node_title: str
variable_pool: ReadOnlyVariablePool
@dataclass(frozen=True)
class PauseRequested:
session_id: str
@dataclass(frozen=True)
class Completed:
selected_handle: str
inputs: Mapping[str, Segment]
outputs: Mapping[str, Segment]
@dataclass(frozen=True)
class Expired:
selected_handle: str
outputs: Mapping[str, Segment]
HITLDecision = PauseRequested | Completed | Expired
HITLCallback = Callable[[HITLContext], HITLDecision]
The pause reason should also be reduced to a minimal host lookup key:
from enum import StrEnum, auto
from typing import Literal
from pydantic import BaseModel
class PauseReasonType(StrEnum):
HITL_REQUIRED = auto()
class HitlRequired(BaseModel):
TYPE: Literal[PauseReasonType.HITL_REQUIRED] = PauseReasonType.HITL_REQUIRED
session_id: str
node_id: str
node_title: str
HumanInputNode should become a pure control-flow translator:
- build
HITLContext
- call
HITLCallback
- translate
PauseRequested into PauseRequestedEvent
- translate
Completed / Expired into StreamCompletedEvent
Scope
- add a callback-based HITL protocol to
graphon
- refactor
HumanInputNode so it depends only on HITLCallback
- minimize
PauseReason for HITL to session_id, node_id, and node_title
- remove host-specific Human Input runtime/form binding protocols from
graphon
- keep host-specific form, delivery, and submission semantics out of
graphon
Non-goals
This issue does not cover host-side work such as:
- HITL session storage
- form snapshots
- token / recipient / delivery behavior
- API or history projection
- replay enrichment
Acceptance criteria
HumanInputNode depends only on the new callback boundary
- HITL pause events no longer expose form schema or delivery-oriented payload
- completed and expired resume paths are driven by
selected_handle plus runtime Segment mappings
graphon no longer exposes Human Input runtime/form repository binding as its public protocol
- tests cover
pause, completed, and expired callback paths
AI disclosure: This issue was drafted and analyzed with Codex using GPT-5.4. I have reviewed the analysis, and I am responsible for the content.
Summary
This issue tracks the
graphonside of a Human-in-the-Loop (HITL) boundary cleanup.The goal is to make
graphonown only the Human Input control-flow state machine, while the host application owns form schemas, session persistence, delivery, submission restoration, and other product-facing HITL semantics.This issue is intentionally limited to
graphon. Host-side changes will be handled separately.Problem
Today,
HumanInputNodestill depends on host-facing runtime and form abstractions, andPauseReasonstill carries host-oriented payload.That has a few practical costs:
graphonneeds to know about form creation/loading semantics.graphonpause payloads include form content, input schema, action schema, and resolved defaults.graphonrestores submitted payloads and owns behavior that should live in the host.graphonand the embedding application.This makes the boundary too large and causes host product semantics to leak back into
graphon.Proposed boundary
The core protocol should be a decision callback:
The pause reason should also be reduced to a minimal host lookup key:
HumanInputNodeshould become a pure control-flow translator:HITLContextHITLCallbackPauseRequestedintoPauseRequestedEventCompleted/ExpiredintoStreamCompletedEventScope
graphonHumanInputNodeso it depends only onHITLCallbackPauseReasonfor HITL tosession_id,node_id, andnode_titlegraphongraphonNon-goals
This issue does not cover host-side work such as:
Acceptance criteria
HumanInputNodedepends only on the new callback boundaryselected_handleplus runtimeSegmentmappingsgraphonno longer exposes Human Input runtime/form repository binding as its public protocolpause,completed, andexpiredcallback paths