Skip to content

langgraph: make is_control_flow_signal public#12

Merged
Nik7A merged 1 commit into
mainfrom
export-control-flow-predicate
Jul 17, 2026
Merged

langgraph: make is_control_flow_signal public#12
Nik7A merged 1 commit into
mainfrom
export-control-flow-predicate

Conversation

@Nik7A

@Nik7A Nik7A commented Jul 17, 2026

Copy link
Copy Markdown
Owner

A host instrumenting its own LangGraph nodes has to separate a node that parked on a control-flow signal from one that crashed. Get it wrong and you sign a false failure — a record claiming a node failed when it merely stopped at a human gate and will re-execute and succeed. This library treats that as exactly as dishonest as a false success, so the predicate is load-bearing for record honesty, not a convenience.

It shipped underscore-prefixed and out of __all__, which left a host two options: import a private name, or restate the boundary itself.

The private import was the right call

bosun imports _is_control_flow_signal (src/bosun/core/audit.py) rather than hand-rolling. Verified against the installed runtime, GraphBubbleUp has four subclasses:

GraphBubbleUp
  GraphDrained
  GraphInterrupt
    NodeInterrupt
  ParentCommand

A hand-rolled isinstance(exc, (GraphInterrupt, ParentCommand)) misses GraphDrained today — and would drift silently the next time the runtime widens the boundary, quietly resuming false-failure signing with nothing to notice. A moved private symbol at least fails loudly at import. The predicate delegates to GraphBubbleUp instead of restating it, so it cannot drift. The only thing wrong with it was the underscore.

What changes

  • is_control_flow_signal is public and in __all__.
  • _is_control_flow_signal stays as an alias. Hosts importing it pin >=0.2,<0.3; removing it inside that range would break their resolve. Drop it in 0.3.
  • The guard enumerates GraphBubbleUp's subclasses from the runtime's class tree rather than listing them — a list in the test would be the same brittle restatement the predicate exists to avoid.

No behaviour change: same function, same callers, new name.

Reported from the bosun migration, 2026-07-17.

A host instrumenting its own LangGraph nodes has to separate a node that
parked on a control-flow signal from one that crashed. Get it wrong and you
sign a false failure — a record claiming a node failed when it merely stopped
at a human gate and will re-execute and succeed. This library treats that as
exactly as dishonest as a false success, so the predicate is load-bearing for
record honesty, not a convenience.

It shipped underscored and out of __all__, which left a host two options:
import a private name, or restate the boundary itself. bosun imports the
private name (core/audit.py) and was right to — a hand-rolled
isinstance(exc, (GraphInterrupt, ParentCommand)) misses GraphDrained today,
and would drift silently the next time the runtime widens the boundary. The
predicate delegates to GraphBubbleUp instead, so it cannot drift. The only
thing wrong with it was the underscore.

The underscore name stays as an alias. Hosts importing it pin >=0.2,<0.3, and
removing it inside that range would break their resolve. Drop it in 0.3.

The guard enumerates GraphBubbleUp's subclasses from the runtime's class tree
rather than listing them, since a list in the test would be the same brittle
restatement the predicate exists to avoid.
@Nik7A
Nik7A merged commit 9d3ebdf into main Jul 17, 2026
1 check passed
@Nik7A
Nik7A deleted the export-control-flow-predicate branch July 17, 2026 11:30
@Nik7A Nik7A mentioned this pull request Jul 17, 2026
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