Skip to content

[pull] main from temporalio:main - #195

Merged
pull[bot] merged 2 commits into
TheTechOddBug:mainfrom
temporalio:main
Aug 11, 2026
Merged

[pull] main from temporalio:main#195
pull[bot] merged 2 commits into
TheTechOddBug:mainfrom
temporalio:main

Conversation

@pull

@pull pull Bot commented Aug 11, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

tconley1428 and others added 2 commits August 11, 2026 11:39
* Add Deep Agents contrib plugin

temporalio.contrib.deepagents makes LangChain Deep Agents durable:
unmodified create_deep_agent(...).ainvoke(...) code runs inside a
workflow with plugins=[DeepAgentsPlugin()], routing every model turn,
I/O tool call, and backend file/shell op through Temporal activities
while the agent loop replays deterministically. Includes explicit
per-tool workflow-vs-activity choice (activity_as_tool /
tool_as_activity), TemporalBackend interception of the full backend
protocol (sync + async + execute) with typed protocol-dataclass
round-trip, continue-as-new state carry via run_deep_agent, streaming
through workflow_streams, and native LangGraph interrupt/resume for
human-in-the-loop. Ships as the temporalio[deepagents] extra
(Python >= 3.11, matching deepagents' own floor).

* Run backend protocol async defaults inline in workflows

deepagents' BackendProtocol implements every async method's DEFAULT as
asyncio.to_thread(sync_twin, ...), and neither StateBackend nor
FilesystemBackend overrides any of them. The deterministic workflow
event loop has no thread executor, so an agent's first built-in tool
call against an unwrapped in-workflow backend — e.g. a model
spontaneously invoking grep on the default state backend — failed the
workflow task with NotImplementedError. Scripted-model tests never
invoke built-ins on the default backend, so only a live-model run
surfaced it.

The plugin now patches the protocol's async defaults at worker start
(same seam pattern as the resolve_model patch): inside a workflow the
sync twin runs inline, which for state-only backends is deterministic
and semantically identical to the upstream default; outside a workflow
the upstream thread-hop default is untouched, as are subclasses that
override an async method natively.

* Return tool content so the node stamps the model's tool_call_id

tool_as_activity returned the ToolMessage assembled inside the
invoke_tool activity, whose tool_call_id is workflow-generated — the
activity cannot know the id the model minted for the call. A real
provider (Anthropic) rejects the next model turn with 400 "unexpected
tool_use_id found in tool_result blocks" because the tool_result does
not pair with any tool_use in the previous message. Offline fakes
never validate the pairing, so only a live-model run surfaced it.

The wrapper now returns the tool result CONTENT and lets the tool
node stamp the model's own tool_call_id — the same path unwrapped
tools take. activity_as_tool already returned raw content and is
unaffected. The regression test records the fake model's second-turn
request and asserts the tool_result rides under the scripted id.

* Satisfy lint on 3.10 and basedpyright's warning gate

Two CI-only failure classes the local scoped runs missed:

- basedpyright fails on warnings repo-wide. Replace deprecated typing
  aliases (Mapping/Sequence/AsyncIterator/Iterator/List/Optional/Union)
  with collections.abc / PEP 604 forms, type the test fixtures the way
  the rest of the suite does, drop unused imports/params, and keep the
  interpreter-floor warning behind a module constant so newer runtimes
  do not narrow it into unreachable code.

- Python 3.10 jobs cannot install deepagents (its floor is 3.11), so
  pyright cannot resolve those imports there. Runtime imports of
  deepagents/langchain in module code go through importlib (attribute
  access on ModuleType is dynamic; monkeypatch writes use setattr),
  and the deepagents test modules carry a file-level pyright directive
  alongside their existing importorskip guards. langchain-core stays
  statically imported — it resolves everywhere via the langgraph extra.

* Fix pydoctor docstring syntax and 3.10 implicit-relative lint

The API-docs build rejects an inline literal whose end-string is
followed by a letter (``Serializable``s), and cannot resolve a
:class: link to the package re-export, so both become plain prose /
literals. On 3.10, where the real deepagents package cannot install,
basedpyright resolves `from deepagents import ...` in
tests/contrib/deepagents/ implicitly relative to the same-named test
directory — extend the existing file-level directive; Python 3 has no
implicit relative imports at runtime and collection is already
importorskip-gated.

* Bind deepagents test symbols via importorskip, not static imports

The previous round's file-level directive used reportImplicitRelativeImport,
which is basedpyright-only vocabulary — plain pyright hard-errors on unknown
rules in pyright comments, taking every lint leg down. Rather than juggle
two checkers' rule sets, drop the static `from deepagents import ...` lines
from the tests entirely: symbols now bind off the module object
pytest.importorskip already returns, which is dynamically typed for every
checker, resolves nothing against the same-named test directory on 3.10,
and lets the directives (and the now-moot protocol cast) be deleted
outright.

* Tighten worker-lifetime cleanup in the deepagents plugin

- Await the cancelled heartbeat task so no pending task outlives an
  activity at event-loop shutdown.
- Catch only ImportError when installing the create_deep_agent patches
  and include the original error in the warning; genuine installation
  bugs now surface at worker startup.
- Keep the LangSmith aio_to_thread override installed for the process
  lifetime: the override slot is global and resetting it would strip a
  composed contrib.langsmith plugin's identical override.
- Unregister a TemporalBackend's registry entry when the wrapper is
  garbage-collected, identity-guarded so a replay's re-registration of
  the same deterministic ref survives the evicted wrapper's cleanup.

* Address review feedback: explicit factory, suggested CAN default, README overhaul

- Add create_temporal_deep_agent(), a thin wrapper over create_deep_agent
  that scopes activity_options to one agent's model calls — the explicit,
  other-plugins-shaped construction path; drop-in vanilla create_deep_agent
  still works.
- run_deep_agent now defaults continue_as_new_after=None to the server's
  is_continue_as_new_suggested() signal (accounts for history size, not
  just count); fixed thresholds remain available.
- README: uv add install, guard-free imports (the plugin's sandbox
  passthrough already made imports_passed_through unnecessary — now proven
  by a test that imports deepagents bare in a workflow module), dedicated
  sections with snippets for tools/backends/HITL/streaming/options, and
  the plugin-ordering claim removed from the composition section.

* Fix CI type-check failures on the Python floor and ceiling

On 3.10 (deepagents not installed) pyright mis-resolves the static
`from deepagents import create_deep_agent` inside the new factory and
reports the symbol uncallable; use the module-attribute access pattern
_model.py already relies on. On 3.14 and latest-deps, basedpyright
gates on warnings and flagged the sandbox-passthrough test's
deliberately-bare `import deepagents` as unused — the noqa only covered
ruff; add the pyright ignore alongside it.

* Fix remaining CI failures: Any-bind the factory, env-gate the suggest-CAN test

Pyright on 3.10 (deepagents absent) resolves the module attribute itself
as an uncallable object, so bind deepagents.create_deep_agent through an
Any-typed local before calling. The suggested-mode continue-as-new e2e
requires the local dev server's low suggestContinueAsNew threshold, so
skip it under the time-skipping environment, following the existing
env_type gating precedent in tests/worker/test_workflow.py.

* Fix implicit-relative import resolution and a pydoctor link target

basedpyright resolves a static `import deepagents` from inside this
same-named package directory (and the same-named test directory) as
implicitly relative when the real package is absent (Python 3.10 CI) —
which is also what produced the earlier uncallable-object error: the
name resolved to our own lazy __getattr__. Load the module through
importlib.import_module in the factory, and rule-ignore the
deliberately-bare import in the sandbox-passthrough test. Also replace
the :class: link to the lazily re-exported TemporalModel with a plain
literal — pydoctor cannot resolve lazy re-exports as link targets and
the API-docs step gates on it.
@pull pull Bot locked and limited conversation to collaborators Aug 11, 2026
@pull pull Bot added the ⤵️ pull label Aug 11, 2026
@pull
pull Bot merged commit dabf0fa into TheTechOddBug:main Aug 11, 2026
4 of 14 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants