Add dynamic node/task summaries to LangGraph plugin#1612
Conversation
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.
There was a problem hiding this comment.
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 | Noneplus plugin-widedefault_summary_fn, with validation against using staticsummaryandsummary_fntogether. - 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.
- 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
left a comment
There was a problem hiding this comment.
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.
What
Adds dynamic, runtime-computed summaries to the LangGraph plugin, matching the
summary_fncapability the Google ADK integration already exposes.summary_fn(args, kwargs) -> str | None, settable via Graph API nodemetadataor Functionalactivity_options[name], plus a plugin-widedefault_summary_fn(overridable per node).execute_in="activity"nodes: the result is set as the activitysummary(carried inuser_metadataon eachActivityTaskScheduledEvent, visible in UI/CLI/history).execute_in="workflow"nodes: there is no activity, so the result updates the workflow's current details viaworkflow.set_current_details(last-writer-wins).summaryactivity option already flowed through toexecute_activity; this is now documented. Setting both a staticsummaryand asummary_fnon the same node raisesValueError.summary_fnruns in workflow context, so it must be deterministic and must not raise. It is replay-safe: summaries ride inuser_metadata, which is not part of command/history matching.Test plan
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_fnraises;summary_fnnot leaked into nodeconfig["metadata"]; workflow-nodecurrent_detailsvia__temporal_workflow_metadata; replay safety.tests/contrib/langgraphsuite: 44 passed. The lone failure,test_continue_as_new, is a pre-existing flake (an asyncio "in select" teardown timing issue that reproduces onmainindependently of this change).ruff,pyright,basedpyright,mypy,pydocstyleclean on the touched files.temporalio/samples-python(tests/langgraph_plugin/) against this branch vs. the released SDK — per-test outcomes identical (9 passed; the 2human_in_the_loopfailures occur in both runs solely from a missingANTHROPIC_API_KEY).