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.
- deepagent_temporal-0.1.0/.env +5 -0
- deepagent_temporal-0.1.0/.env.ci +5 -0
- deepagent_temporal-0.1.0/.github/workflows/ci.yml +67 -0
- deepagent_temporal-0.1.0/.github/workflows/docs.yml +50 -0
- deepagent_temporal-0.1.0/.github/workflows/release.yml +35 -0
- deepagent_temporal-0.1.0/.gitignore +13 -0
- deepagent_temporal-0.1.0/Makefile +84 -0
- deepagent_temporal-0.1.0/PKG-INFO +243 -0
- deepagent_temporal-0.1.0/README.md +230 -0
- deepagent_temporal-0.1.0/deepagent_temporal/__init__.py +18 -0
- deepagent_temporal-0.1.0/deepagent_temporal/agent.py +240 -0
- deepagent_temporal-0.1.0/deepagent_temporal/config.py +26 -0
- deepagent_temporal-0.1.0/deepagent_temporal/middleware.py +140 -0
- deepagent_temporal-0.1.0/docker-compose.yml +135 -0
- deepagent_temporal-0.1.0/docs/DESIGN.md +1173 -0
- deepagent_temporal-0.1.0/docs/REQUIREMENTS.md +600 -0
- deepagent_temporal-0.1.0/docs/getting-started/installation.md +57 -0
- deepagent_temporal-0.1.0/docs/getting-started/quickstart.md +107 -0
- deepagent_temporal-0.1.0/docs/guides/concepts.md +70 -0
- deepagent_temporal-0.1.0/docs/guides/human-in-the-loop.md +78 -0
- deepagent_temporal-0.1.0/docs/guides/local-development.md +67 -0
- deepagent_temporal-0.1.0/docs/guides/sub-agents.md +90 -0
- deepagent_temporal-0.1.0/docs/guides/worker-affinity.md +68 -0
- deepagent_temporal-0.1.0/docs/index.md +42 -0
- deepagent_temporal-0.1.0/docs/reference/agent.md +21 -0
- deepagent_temporal-0.1.0/docs/reference/config.md +5 -0
- deepagent_temporal-0.1.0/docs/reference/middleware.md +23 -0
- deepagent_temporal-0.1.0/dynamicconfig/development-sql.yaml +6 -0
- deepagent_temporal-0.1.0/examples/deepagent_temporal_example.py +125 -0
- deepagent_temporal-0.1.0/mkdocs.yml +102 -0
- deepagent_temporal-0.1.0/pyproject.toml +85 -0
- deepagent_temporal-0.1.0/scripts/create-namespace.sh +27 -0
- deepagent_temporal-0.1.0/scripts/setup-postgres-es.sh +54 -0
- deepagent_temporal-0.1.0/tests/conftest.py +45 -0
- deepagent_temporal-0.1.0/tests/test_deepagent_agent.py +233 -0
- deepagent_temporal-0.1.0/tests/test_deepagent_e2e.py +416 -0
- deepagent_temporal-0.1.0/tests/test_deepagent_integration.py +279 -0
- deepagent_temporal-0.1.0/tests/test_deepagent_middleware.py +170 -0
|
@@ -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,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
|