#11659·langchainjs

Let the HITL `when` predicate read tool metadata, and add an `interruptOn` catch-all

Author: dolorsyCreated Sep 17, 2026Updated Sep 17, 2026

Summary

humanInTheLoopMiddleware (in the langchain package) gained a when predicate on InterruptOnConfig in #11012, but two gaps prevent using it for property-based gating — e.g. "pause before any tool whose metadata marks it destructive":

A. when receives tool: undefined. In the afterModel path the ToolCallRequest is built with tool set to undefined, so a predicate can only read toolCall (name + args), state, and runtime — never the tool's description/schema/metadata.annotations.

B. interruptOn is keyed strictly by tool name, with no catch-all. There is no wildcard/default entry, so you cannot express "apply this predicate to every tool" — which is required when tool names are not known ahead of time (e.g. tools discovered dynamically/lazily).

Together, even with when present you can neither target unknown-named tools nor read their metadata.

Proposal

Two small, backward-compatible changes:

  1. Populate request.tool in the when path. Expose the agent's registered tools to middleware hooks (via a new optional runtime.tools) and resolve the tool by name so when can read its metadata. Dynamically-registered tools still resolve to undefined (unchanged semantics).
  2. Add a "*" catch-all key to interruptOn, applied to any tool without an explicit entry. An exact tool-name entry always wins; an explicit false opts a tool out even when "*" is present; absent + no wildcard stays auto-approve, so existing configs are unchanged.

Example:

typescript
humanInTheLoopMiddleware({
  interruptOn: {
    // Pause before any tool whose metadata marks it destructive,
    // even when tool names are not enumerated upfront.
    "*": {
      allowedDecisions: ["approve", "reject"],
      when: (request) =>
        (request.tool as {
          metadata?: { annotations?: { destructiveHint?: boolean } };
        })?.metadata?.annotations?.destructiveHint === true,
    },
    // An exact-name entry overrides the "*" catch-all.
    read_file: false,
  },
});

Parity note

The Python langchain package currently does neither (its when also receives tool=None, and it has no catch-all), so this is a JS-first enhancement rather than parity. Happy to open a matching Python PR if maintainers would like the two aligned.

Willing to contribute

Yes — I have a branch with the implementation, unit tests, and a type test ready to open as a PR referencing this issue.

Source: langchain-ai/langchainjs