Skip to content

Add dynamic node/task summaries to LangGraph plugin#1612

Open
DABH wants to merge 4 commits into
mainfrom
langgraph-dynamic-summaries
Open

Add dynamic node/task summaries to LangGraph plugin#1612
DABH wants to merge 4 commits into
mainfrom
langgraph-dynamic-summaries

Conversation

@DABH

@DABH DABH commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What

Adds dynamic, runtime-computed summaries to the LangGraph plugin, matching the summary_fn capability the Google ADK integration already exposes.

  • New per-node / per-task summary_fn(args, kwargs) -> str | None, settable via Graph API node metadata or Functional activity_options[name], plus a plugin-wide default_summary_fn (overridable per node).
  • execute_in="activity" nodes: the result is set as the activity summary (carried in user_metadata on each ActivityTaskScheduledEvent, visible in UI/CLI/history).
  • execute_in="workflow" nodes: there is no activity, so the result updates the workflow's current details via workflow.set_current_details (last-writer-wins).
  • The static summary activity option already flowed through to execute_activity; this is now documented. Setting both a static summary and a summary_fn on the same node raises ValueError.
  • summary_fn runs in workflow context, so it must be deterministic and must not raise. It is replay-safe: summaries ride in user_metadata, which is not part of command/history matching.

Test plan

  • New tests/contrib/langgraph/test_summary_fn.py (11 tests): activity summary in history; dynamic/None/empty variants; default_summary_fn + per-node override; static-suppresses-default; static+summary_fn raises; summary_fn not leaked into node config["metadata"]; workflow-node current_details via __temporal_workflow_metadata; replay safety.
  • tests/contrib/langgraph suite: 44 passed. The lone failure, test_continue_as_new, is a pre-existing flake (an asyncio "in select" teardown timing issue that reproduces on main independently of this change).
  • ruff, pyright, basedpyright, mypy, pydocstyle clean on the touched files.
  • Ran the LangGraph samples in temporalio/samples-python (tests/langgraph_plugin/) against this branch vs. the released SDK — per-test outcomes identical (9 passed; the 2 human_in_the_loop failures occur in both runs solely from a missing ANTHROPIC_API_KEY).

Adds a per-node/per-task summary_fn(args, kwargs) -> str | None (and a
plugin-wide default_summary_fn) that computes a Temporal summary at
runtime from the node's input.

- execute_in="activity" nodes: the result sets the activity summary
  (user_metadata, shown on each scheduled-activity event).
- execute_in="workflow" nodes: the result updates the workflow's current
  details via workflow.set_current_details (last-writer-wins).

A static summary activity option already flowed through to
execute_activity; this is now documented. Setting both a static summary
and summary_fn on the same node raises ValueError. summary_fn runs in
workflow context (must be deterministic and must not raise) and is
replay-safe, since summaries ride in user_metadata.
@DABH DABH requested review from a team as code owners June 22, 2026 19:53
Comment thread temporalio/contrib/langgraph/_plugin.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds runtime-computed node/task summaries to the temporalio.contrib.langgraph plugin, aligning it with existing dynamic-summary capabilities in other contrib integrations and improving observability in Temporal UI/CLI.

Changes:

  • Introduces per-node/per-task summary_fn(args, kwargs) -> str | None plus plugin-wide default_summary_fn, with validation against using static summary and summary_fn together.
  • Plumbs computed summaries into activity scheduling (execute_in="activity") and into workflow current details (execute_in="workflow").
  • Adds documentation and a dedicated test suite covering activity-history summaries, defaults/overrides, metadata stripping, workflow current details, and replay behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/contrib/langgraph/test_summary_fn.py New tests validating summary_fn behavior for activity- and workflow-executed nodes, plus replay safety.
temporalio/contrib/langgraph/README.md Documents static summaries and new dynamic summary_fn/default_summary_fn behavior.
temporalio/contrib/langgraph/_workflow.py Adds workflow-side summary_fn support via workflow.set_current_details.
temporalio/contrib/langgraph/_plugin.py Adds default_summary_fn, plumbs per-node/task summary_fn, and strips it from node metadata.
temporalio/contrib/langgraph/_activity.py Computes dynamic activity summary on schedule path and passes it to workflow.execute_activity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread temporalio/contrib/langgraph/_workflow.py Outdated
Comment thread temporalio/contrib/langgraph/README.md
DABH added 2 commits June 29, 2026 17:19
- Drop the plugin-level default_summary_fn; summary_fn is now set only
  per node (Graph metadata) / per task (activity_options), matching the
  ADK plugin's per-model scoping so each node manages its own summary.
- Normalize a static summary into a summary_fn (a constant function), so
  static and dynamic summaries share one resolution path feeding the
  activity summary kwarg / set_current_details.
- Workflow-bound nodes now clear current details when summary_fn returns
  empty, so a node no longer shows a stale summary from an earlier node.

@xumaple xumaple left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both summary and summary_fn are in _LANGGRAPH_OPTION_KEYS, so either can come from the defaults or from the node. Because they're different dict keys, a default and a per-node value don't override each other — they coexist in the merged opts, and then the guard sees both and raises. Concretely:

  • default_activity_options={"summary_fn": global_fn} + one node sets a static summary → ValueError
  • default_activity_options={"summary": "label"} + one node sets summary_fn → ValueError

That first case is plausible: a single dynamic summarizer as the default, with one node wanting a fixed label. Today you can't express "node-level setting overrides the inherited default" — mixing the two types anywhere in the resolved options is rejected, which is stricter than ADK's "don't set both on the same object."

Consider resolving based on hierarchy? If a node supplies either key, drop the inherited other-key from the defaults first, so the more-specific node setting wins (the normal layering expectation). The guard then only fires when both are set at the same level. Or some other way of achieving this same of allowing a default of one type and an override of another type.

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.

4 participants