deepagent-temporal 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. deepagent_temporal-0.1.0/.env +5 -0
  2. deepagent_temporal-0.1.0/.env.ci +5 -0
  3. deepagent_temporal-0.1.0/.github/workflows/ci.yml +67 -0
  4. deepagent_temporal-0.1.0/.github/workflows/docs.yml +50 -0
  5. deepagent_temporal-0.1.0/.github/workflows/release.yml +35 -0
  6. deepagent_temporal-0.1.0/.gitignore +13 -0
  7. deepagent_temporal-0.1.0/Makefile +84 -0
  8. deepagent_temporal-0.1.0/PKG-INFO +243 -0
  9. deepagent_temporal-0.1.0/README.md +230 -0
  10. deepagent_temporal-0.1.0/deepagent_temporal/__init__.py +18 -0
  11. deepagent_temporal-0.1.0/deepagent_temporal/agent.py +240 -0
  12. deepagent_temporal-0.1.0/deepagent_temporal/config.py +26 -0
  13. deepagent_temporal-0.1.0/deepagent_temporal/middleware.py +140 -0
  14. deepagent_temporal-0.1.0/docker-compose.yml +135 -0
  15. deepagent_temporal-0.1.0/docs/DESIGN.md +1173 -0
  16. deepagent_temporal-0.1.0/docs/REQUIREMENTS.md +600 -0
  17. deepagent_temporal-0.1.0/docs/getting-started/installation.md +57 -0
  18. deepagent_temporal-0.1.0/docs/getting-started/quickstart.md +107 -0
  19. deepagent_temporal-0.1.0/docs/guides/concepts.md +70 -0
  20. deepagent_temporal-0.1.0/docs/guides/human-in-the-loop.md +78 -0
  21. deepagent_temporal-0.1.0/docs/guides/local-development.md +67 -0
  22. deepagent_temporal-0.1.0/docs/guides/sub-agents.md +90 -0
  23. deepagent_temporal-0.1.0/docs/guides/worker-affinity.md +68 -0
  24. deepagent_temporal-0.1.0/docs/index.md +42 -0
  25. deepagent_temporal-0.1.0/docs/reference/agent.md +21 -0
  26. deepagent_temporal-0.1.0/docs/reference/config.md +5 -0
  27. deepagent_temporal-0.1.0/docs/reference/middleware.md +23 -0
  28. deepagent_temporal-0.1.0/dynamicconfig/development-sql.yaml +6 -0
  29. deepagent_temporal-0.1.0/examples/deepagent_temporal_example.py +125 -0
  30. deepagent_temporal-0.1.0/mkdocs.yml +102 -0
  31. deepagent_temporal-0.1.0/pyproject.toml +85 -0
  32. deepagent_temporal-0.1.0/scripts/create-namespace.sh +27 -0
  33. deepagent_temporal-0.1.0/scripts/setup-postgres-es.sh +54 -0
  34. deepagent_temporal-0.1.0/tests/conftest.py +45 -0
  35. deepagent_temporal-0.1.0/tests/test_deepagent_agent.py +233 -0
  36. deepagent_temporal-0.1.0/tests/test_deepagent_e2e.py +416 -0
  37. deepagent_temporal-0.1.0/tests/test_deepagent_integration.py +279 -0
  38. deepagent_temporal-0.1.0/tests/test_deepagent_middleware.py +170 -0
@@ -0,0 +1,5 @@
1
+ TEMPORAL_VERSION=1.26.2
2
+ TEMPORAL_ADMINTOOLS_VERSION=1.26.2
3
+ TEMPORAL_UI_VERSION=2.36.0
4
+ POSTGRESQL_VERSION=16
5
+ ELASTICSEARCH_VERSION=7.17.28
@@ -0,0 +1,5 @@
1
+ TEMPORAL_VERSION=1.26.2
2
+ TEMPORAL_ADMINTOOLS_VERSION=1.26.2
3
+ TEMPORAL_UI_VERSION=2.36.0
4
+ POSTGRESQL_VERSION=16
5
+ ELASTICSEARCH_VERSION=7.17.28
@@ -0,0 +1,67 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: astral-sh/setup-uv@v4
15
+ with:
16
+ version: "latest"
17
+ - run: uv sync
18
+ - run: make lint
19
+
20
+ test:
21
+ runs-on: ubuntu-latest
22
+ strategy:
23
+ matrix:
24
+ python-version: ["3.10", "3.11", "3.12"]
25
+ steps:
26
+ - uses: actions/checkout@v4
27
+ - uses: astral-sh/setup-uv@v4
28
+ with:
29
+ version: "latest"
30
+ python-version: ${{ matrix.python-version }}
31
+ - run: uv sync
32
+ - run: make test
33
+
34
+ integration-test:
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+
39
+ - name: Set up environment
40
+ run: cp .env.ci .env
41
+
42
+ - name: Configure vm.max_map_count for Elasticsearch
43
+ run: sudo sysctl -w vm.max_map_count=262144
44
+
45
+ - name: Start Temporal
46
+ run: make start_temporal
47
+
48
+ - name: Wait for Temporal to be ready
49
+ run: make wait_temporal
50
+
51
+ - uses: astral-sh/setup-uv@v4
52
+ with:
53
+ version: "latest"
54
+ - run: uv sync
55
+
56
+ - name: Run integration tests
57
+ run: make test_integration
58
+ env:
59
+ TEMPORAL_ADDRESS: localhost:7233
60
+
61
+ - name: Show docker compose logs
62
+ if: failure()
63
+ run: docker compose logs
64
+
65
+ - name: Tear down Temporal
66
+ if: always()
67
+ run: make stop_temporal
@@ -0,0 +1,50 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+ pages: write
12
+ id-token: write
13
+
14
+ # Only allow one deployment at a time
15
+ concurrency:
16
+ group: "pages"
17
+ cancel-in-progress: false
18
+
19
+ jobs:
20
+ build:
21
+ runs-on: ubuntu-latest
22
+ steps:
23
+ - uses: actions/checkout@v4
24
+
25
+ - uses: astral-sh/setup-uv@v4
26
+ with:
27
+ version: "latest"
28
+
29
+ - run: uv sync --group docs
30
+
31
+ - name: Build docs
32
+ run: uv run mkdocs build --strict
33
+
34
+ - name: Upload artifact
35
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
36
+ uses: actions/upload-pages-artifact@v3
37
+ with:
38
+ path: site
39
+
40
+ deploy:
41
+ if: github.ref == 'refs/heads/main' && github.event_name == 'push'
42
+ needs: build
43
+ runs-on: ubuntu-latest
44
+ environment:
45
+ name: github-pages
46
+ url: ${{ steps.deployment.outputs.page_url }}
47
+ steps:
48
+ - name: Deploy to GitHub Pages
49
+ id: deployment
50
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,35 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ id-token: write
13
+ contents: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: astral-sh/setup-uv@v4
18
+ with:
19
+ version: "latest"
20
+
21
+ - name: Set version from tag
22
+ run: |
23
+ VERSION=${GITHUB_REF_NAME#v}
24
+ sed -i "s/^version = .*/version = \"${VERSION}\"/" pyproject.toml
25
+
26
+ - name: Build package
27
+ run: make build
28
+
29
+ - name: Publish to PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1
31
+
32
+ - name: Create GitHub Release
33
+ uses: softprops/action-gh-release@v2
34
+ with:
35
+ generate_release_notes: true
@@ -0,0 +1,13 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .mypy_cache/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ *.egg
11
+ .venv/
12
+ uv.lock
13
+ site/
@@ -0,0 +1,84 @@
1
+ .PHONY: test test_watch test_integration lint type format build start_temporal stop_temporal wait_temporal test_integration_docker docs docs_serve
2
+
3
+ ######################
4
+ # TESTING AND COVERAGE
5
+ ######################
6
+
7
+ TEST ?= .
8
+
9
+ test:
10
+ uv run pytest $(TEST)
11
+
12
+ test_integration:
13
+ uv run pytest -m integration $(TEST)
14
+
15
+ test_watch:
16
+ uv run ptw $(TEST)
17
+
18
+ ######################
19
+ # LINTING AND FORMATTING
20
+ ######################
21
+
22
+ # Define a variable for Python and notebook files.
23
+ PYTHON_FILES=.
24
+ MYPY_CACHE=.mypy_cache
25
+ lint format: PYTHON_FILES=.
26
+ lint_diff format_diff: PYTHON_FILES=$(shell git diff --name-only --relative --diff-filter=d main . | grep -E '\.py$$|\.ipynb$$')
27
+ lint_package: PYTHON_FILES=deepagent_temporal
28
+ lint_tests: PYTHON_FILES=tests
29
+ lint_tests: MYPY_CACHE=.mypy_cache_test
30
+
31
+ lint lint_diff lint_package lint_tests:
32
+ uv run ruff check .
33
+ [ "$(PYTHON_FILES)" = "" ] || uv run ruff format $(PYTHON_FILES) --diff
34
+ [ "$(PYTHON_FILES)" = "" ] || uv run ruff check --select I $(PYTHON_FILES)
35
+ [ "$(PYTHON_FILES)" = "" ] || mkdir -p $(MYPY_CACHE)
36
+ [ "$(PYTHON_FILES)" = "" ] || uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
37
+
38
+ type:
39
+ mkdir -p $(MYPY_CACHE) && uv run mypy $(PYTHON_FILES) --cache-dir $(MYPY_CACHE)
40
+
41
+ format format_diff:
42
+ uv run ruff format $(PYTHON_FILES)
43
+ uv run ruff check --select I --fix $(PYTHON_FILES)
44
+
45
+ ######################
46
+ # DOCS
47
+ ######################
48
+
49
+ docs:
50
+ uv run mkdocs build --strict
51
+
52
+ docs_serve:
53
+ uv run mkdocs serve
54
+
55
+ ######################
56
+ # BUILD
57
+ ######################
58
+
59
+ build:
60
+ uv build
61
+
62
+ ######################
63
+ # DOCKER TEMPORAL
64
+ ######################
65
+
66
+ TEMPORAL_GRPC_PORT ?= 7233
67
+
68
+ start_temporal:
69
+ TEMPORAL_GRPC_PORT=$(TEMPORAL_GRPC_PORT) docker compose up -d
70
+
71
+ stop_temporal:
72
+ docker compose down
73
+
74
+ wait_temporal:
75
+ @echo "Waiting for Temporal server to be ready..."
76
+ @timeout=120; while [ "$$(docker inspect -f '{{.State.Status}}' temporal-create-namespace 2>/dev/null)" != "exited" ] || \
77
+ [ "$$(docker inspect -f '{{.State.ExitCode}}' temporal-create-namespace 2>/dev/null)" != "0" ]; do \
78
+ sleep 2; timeout=$$((timeout - 2)); \
79
+ if [ $$timeout -le 0 ]; then echo "Temporal failed to start"; docker compose logs; exit 1; fi; \
80
+ done
81
+ @echo "Temporal server is ready."
82
+
83
+ test_integration_docker: start_temporal wait_temporal
84
+ TEMPORAL_ADDRESS=localhost:$(TEMPORAL_GRPC_PORT) uv run pytest -m integration $(TEST)
@@ -0,0 +1,243 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepagent-temporal
3
+ Version: 0.1.0
4
+ Summary: Temporal integration for Deep Agents - durable execution for AI agent workflows.
5
+ Project-URL: Source, https://github.com/pradithya/deepagent-temporal
6
+ Project-URL: Documentation, https://pradithya.github.io/deepagent-temporal
7
+ License-Expression: MIT
8
+ Requires-Python: >=3.10
9
+ Requires-Dist: langgraph-temporal>=0.2.0
10
+ Requires-Dist: langgraph>=1.0.0
11
+ Requires-Dist: temporalio>=1.7.0
12
+ Description-Content-Type: text/markdown
13
+
14
+ # deepagent-temporal
15
+
16
+ Temporal integration for [Deep Agents](https://github.com/langchain-ai/deepagents) — durable execution for AI agent workflows.
17
+
18
+ If your Deep Agent process crashes mid-task, all progress is lost. Sub-agents are ephemeral. Human-in-the-loop approval blocks a running process. `deepagent-temporal` solves these problems by running your Deep Agent as a [Temporal](https://temporal.io) Workflow:
19
+
20
+ - **Durable execution** — survives process crashes, restarts, and deployments
21
+ - **Sub-agent dispatch** — sub-agents run as independent Temporal Child Workflows
22
+ - **Worker affinity** — sticky task queues keep file operations on the same machine, side stepping the need of NFS or shared storage.
23
+ - **Zero-resource HITL** — workflow pauses consume no compute while waiting for approval
24
+
25
+ > This project is experimental, use at your own risk
26
+
27
+ ## Installation
28
+
29
+ ```bash
30
+ pip install deepagent-temporal
31
+ ```
32
+
33
+ Requires Python 3.10+, [langgraph-temporal](https://github.com/pradithya/langgraph-temporal) >= 0.1.0, and a running Temporal server.
34
+
35
+ ## Quick Start
36
+
37
+ ### Before: vanilla Deep Agent
38
+
39
+ ```python
40
+ from deepagents import create_deep_agent
41
+ from deepagents.backends import FilesystemBackend
42
+ from langchain_anthropic import ChatAnthropic
43
+
44
+ agent = create_deep_agent(
45
+ model=ChatAnthropic(model="claude-sonnet-4-20250514"),
46
+ tools=[read_file, write_file, execute],
47
+ system_prompt="You are a helpful coding assistant.",
48
+ backend=FilesystemBackend(root_dir="/workspace"),
49
+ )
50
+
51
+ # No durability — if the process crashes, all progress is lost.
52
+ # Sub-agents run in-process. HITL blocks a live process.
53
+ result = await agent.ainvoke(
54
+ {"messages": [HumanMessage(content="Fix the bug in main.py")]},
55
+ config={"configurable": {"thread_id": "task-123"}},
56
+ )
57
+ ```
58
+
59
+ ### After: Temporal-backed Deep Agent
60
+
61
+ ```python
62
+ from datetime import timedelta
63
+ from deepagents import create_deep_agent
64
+ from deepagents.backends import FilesystemBackend
65
+ from langchain_anthropic import ChatAnthropic
66
+ from temporalio.client import Client
67
+
68
+ from deepagent_temporal import TemporalDeepAgent
69
+
70
+ # 1. Create your agent exactly as before
71
+ agent = create_deep_agent(
72
+ model=ChatAnthropic(model="claude-sonnet-4-20250514"),
73
+ tools=[read_file, write_file, execute],
74
+ system_prompt="You are a helpful coding assistant.",
75
+ backend=FilesystemBackend(root_dir="/workspace"),
76
+ )
77
+
78
+ # 2. Connect to Temporal and wrap the agent
79
+ client = await Client.connect("localhost:7233")
80
+ temporal_agent = TemporalDeepAgent(
81
+ agent,
82
+ client,
83
+ task_queue="coding-agents",
84
+ use_worker_affinity=True, # automatic worker pinning
85
+ )
86
+
87
+ # 3. Same API — now with durable execution
88
+ result = await temporal_agent.ainvoke(
89
+ {"messages": [HumanMessage(content="Fix the bug in main.py")]},
90
+ config={"configurable": {"thread_id": "task-123"}},
91
+ )
92
+ ```
93
+
94
+ The `ainvoke`, `astream`, `get_state`, and `resume` APIs are identical. Your existing code changes by three lines.
95
+
96
+ ## Running a Worker
97
+
98
+ The agent graph executes on a Temporal Worker. Run this in a separate process (or on a dedicated machine for filesystem affinity):
99
+
100
+ ```python
101
+ import asyncio
102
+ from temporalio.client import Client
103
+ from temporalio.worker import UnsandboxedWorkflowRunner
104
+
105
+ from deepagent_temporal import TemporalDeepAgent
106
+
107
+ async def main():
108
+ agent = create_deep_agent(...) # same setup as above
109
+
110
+ client = await Client.connect("localhost:7233")
111
+ temporal_agent = TemporalDeepAgent(
112
+ agent, client,
113
+ task_queue="coding-agents",
114
+ use_worker_affinity=True,
115
+ )
116
+
117
+ # create_worker() returns a WorkerGroup with two internal workers:
118
+ # one on the shared queue (Workflows + discovery) and one on a
119
+ # unique queue (node Activities).
120
+ worker = temporal_agent.create_worker(
121
+ workflow_runner=UnsandboxedWorkflowRunner(),
122
+ )
123
+ async with worker:
124
+ print("Worker running. Ctrl+C to stop.")
125
+ await asyncio.Future() # run forever
126
+
127
+ asyncio.run(main())
128
+ ```
129
+
130
+ ## Worker Affinity via Worker-Specific Task Queues
131
+
132
+ Deep Agents often use `FilesystemBackend` — tools read and write files on the local disk. All Activities for an agent must run on the same worker to keep the filesystem consistent.
133
+
134
+ Enable `use_worker_affinity=True` and the framework handles it automatically following the [Temporal worker-specific task queues pattern](https://github.com/temporalio/samples-python/tree/main/worker_specific_task_queues):
135
+
136
+ ```python
137
+ temporal_agent = TemporalDeepAgent(
138
+ agent, client,
139
+ task_queue="coding-agents",
140
+ use_worker_affinity=True, # transparent to the client
141
+ )
142
+ ```
143
+
144
+ **How it works:**
145
+ 1. `create_worker()` generates a unique queue name per worker process and starts two internal workers: one on the shared queue (Workflows + discovery Activity), one on its unique queue (node Activities)
146
+ 2. When a Workflow starts, it calls a `get_available_task_queue` Activity on the shared queue — whichever worker picks it up returns its unique queue
147
+ 3. All subsequent node Activities are dispatched to that discovered queue
148
+ 4. The discovered queue survives `continue-as-new` — the same worker stays pinned across workflow runs
149
+ 5. HITL waits have no timeout concern — the queue persists independently
150
+
151
+ The client never needs to know queue names. Workers self-register.
152
+
153
+ ## Sub-Agents as Child Workflows
154
+
155
+ Deep Agents can spawn sub-agents via the `task` tool. With `deepagent-temporal`, each sub-agent runs as an independent Temporal Child Workflow with its own durability, timeout, and observability.
156
+
157
+ ### Setting up the middleware
158
+
159
+ `TemporalSubAgentMiddleware` intercepts `task` tool calls and dispatches them as Child Workflows instead of running them in-process. Inject it **before** graph compilation:
160
+
161
+ ```python
162
+ from deepagent_temporal import TemporalSubAgentMiddleware
163
+
164
+ middleware = TemporalSubAgentMiddleware(
165
+ subagent_specs={
166
+ "researcher": "subagent:researcher",
167
+ "coder": "subagent:coder",
168
+ },
169
+ )
170
+
171
+ agent = create_deep_agent(
172
+ model=model,
173
+ tools=tools,
174
+ middleware=[middleware], # inject before compilation
175
+ # ... other params
176
+ )
177
+ ```
178
+
179
+ ### Configuring sub-agent execution
180
+
181
+ ```python
182
+ temporal_agent = TemporalDeepAgent(
183
+ agent, client,
184
+ task_queue="main-agents",
185
+ subagent_task_queue="sub-agents", # separate queue for sub-agents
186
+ subagent_execution_timeout=timedelta(minutes=15), # per sub-agent timeout
187
+ )
188
+ ```
189
+
190
+ When the LLM invokes the `task` tool, the middleware stores a `SubAgentRequest` in a context variable. The Activity collects it, and the Workflow dispatches a Child Workflow. The result flows back as a `ToolMessage` to the parent agent — exactly matching the behavior of the in-process `SubAgentMiddleware`.
191
+
192
+ ## Human-in-the-Loop
193
+
194
+ Deep Agents' `interrupt()` works out of the box. The workflow pauses with zero resource consumption and resumes when you send a Signal:
195
+
196
+ ```python
197
+ # Start the agent (non-blocking)
198
+ handle = await temporal_agent.astart(
199
+ {"messages": [HumanMessage(content="Refactor auth module")]},
200
+ config={"configurable": {"thread_id": "task-456"}},
201
+ )
202
+
203
+ # ... later, check if it's waiting for approval
204
+ state = await temporal_agent.get_state(
205
+ {"configurable": {"thread_id": "task-456"}}
206
+ )
207
+ if state["status"] == "interrupted":
208
+ print("Pending approval:", state["interrupts"])
209
+
210
+ # Approve and resume
211
+ await temporal_agent.resume(
212
+ {"configurable": {"thread_id": "task-456"}},
213
+ "approved",
214
+ )
215
+ ```
216
+
217
+ ## Local Development
218
+
219
+ For testing without a Temporal server deployment:
220
+
221
+ ```python
222
+ temporal_agent = await TemporalDeepAgent.local(agent)
223
+ result = await temporal_agent.ainvoke({"messages": ["hello"]})
224
+ ```
225
+
226
+ This starts an in-process Temporal test server automatically.
227
+
228
+ ## Testing
229
+
230
+ ```bash
231
+ # Unit + integration tests (uses in-process Temporal test server)
232
+ make test
233
+
234
+ # Integration tests only
235
+ make test_integration
236
+
237
+ # Integration tests against Dockerized Temporal
238
+ make test_integration_docker
239
+ ```
240
+
241
+ ## License
242
+
243
+ MIT