[pull] main from temporalio:main - #195
Merged
Merged
Conversation
* 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 : )