temporal-agent-harness 0.3.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 (96) hide show
  1. temporal_agent_harness-0.3.0/LICENSE +21 -0
  2. temporal_agent_harness-0.3.0/PKG-INFO +566 -0
  3. temporal_agent_harness-0.3.0/README.md +523 -0
  4. temporal_agent_harness-0.3.0/pyproject.toml +165 -0
  5. temporal_agent_harness-0.3.0/setup.cfg +4 -0
  6. temporal_agent_harness-0.3.0/temporal_agent_harness/__init__.py +15 -0
  7. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/__init__.py +11 -0
  8. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/__init__.py +67 -0
  9. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_gemini_activity.py +235 -0
  10. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_google_genai_plugin.py +98 -0
  11. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_interactions_activity.py +411 -0
  12. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_interactions_models.py +31 -0
  13. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_interactions_workflow.py +56 -0
  14. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_models.py +121 -0
  15. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_api_client.py +292 -0
  16. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_async_client.py +64 -0
  17. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_file_search_stores.py +109 -0
  18. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_files.py +169 -0
  19. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/_temporal_interactions.py +143 -0
  20. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/google_genai_plugin/workflow.py +96 -0
  21. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/integration_helpers/__init__.py +33 -0
  22. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/integration_helpers/_stream_observer.py +170 -0
  23. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/__init__.py +33 -0
  24. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_heartbeat_decorator.py +38 -0
  25. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_invoke_model_activity.py +451 -0
  26. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_mcp.py +537 -0
  27. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_model_parameters.py +127 -0
  28. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_nexus_mcp.py +194 -0
  29. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_openai_runner.py +378 -0
  30. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_otel_trace_interceptor.py +88 -0
  31. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_temporal_model_stub.py +317 -0
  32. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_temporal_openai_agents.py +426 -0
  33. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_temporal_trace_provider.py +211 -0
  34. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/_trace_interceptor.py +445 -0
  35. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/sandbox/__init__.py +1 -0
  36. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/sandbox/_sandbox_client_provider.py +285 -0
  37. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/sandbox/_temporal_activity_models.py +218 -0
  38. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/sandbox/_temporal_sandbox_client.py +124 -0
  39. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/sandbox/_temporal_sandbox_session.py +239 -0
  40. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/testing.py +262 -0
  41. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents/workflow.py +435 -0
  42. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/openai_agents_harness.py +512 -0
  43. temporal_agent_harness-0.3.0/temporal_agent_harness/ai_sdks/pydantic_ai_harness.py +512 -0
  44. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/__init__.py +18 -0
  45. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/agent.py +78 -0
  46. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/agent_client.py +650 -0
  47. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/agent_protocol/__init__.py +166 -0
  48. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/agent_protocol/agent_interface.py +553 -0
  49. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/agent_protocol/events.py +896 -0
  50. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/agent_protocol/subagent_interface.py +103 -0
  51. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/agent_workflow.py +2904 -0
  52. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/code_mode/__init__.py +30 -0
  53. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/code_mode/activities.py +123 -0
  54. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/code_mode/batch_models.py +84 -0
  55. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/code_mode/driver.py +203 -0
  56. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/code_mode/monty_stepper.py +176 -0
  57. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/code_mode/stubs.py +325 -0
  58. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/code_mode/tool.py +197 -0
  59. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/slash_commands.py +378 -0
  60. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/stream_context.py +30 -0
  61. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/stream_merge/__init__.py +32 -0
  62. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/stream_merge/cursor.py +166 -0
  63. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/stream_merge/gates.py +148 -0
  64. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/stream_merge/merge.py +494 -0
  65. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/subagent_activities.py +317 -0
  66. temporal_agent_harness-0.3.0/temporal_agent_harness/harness/subagent_toolset.py +203 -0
  67. temporal_agent_harness-0.3.0/temporal_agent_harness/nexus_agent_adapter/__init__.py +0 -0
  68. temporal_agent_harness-0.3.0/temporal_agent_harness/nexus_agent_adapter/generated/__init__.py +65 -0
  69. temporal_agent_harness-0.3.0/temporal_agent_harness/nexus_agent_adapter/generated/_definitions.py +514 -0
  70. temporal_agent_harness-0.3.0/temporal_agent_harness/nexus_agent_adapter/generated/models.py +2192 -0
  71. temporal_agent_harness-0.3.0/temporal_agent_harness/nexus_agent_adapter/generated/services.py +101 -0
  72. temporal_agent_harness-0.3.0/temporal_agent_harness/nexus_agent_adapter/handler.py +369 -0
  73. temporal_agent_harness-0.3.0/temporal_agent_harness/nexus_agent_adapter/worker.py +72 -0
  74. temporal_agent_harness-0.3.0/temporal_agent_harness/plugin.py +255 -0
  75. temporal_agent_harness-0.3.0/temporal_agent_harness/ui/__init__.py +12 -0
  76. temporal_agent_harness-0.3.0/temporal_agent_harness/ui/dist/assets/index-CSosuMGP.js +53 -0
  77. temporal_agent_harness-0.3.0/temporal_agent_harness/ui/dist/assets/index-DNXWrDgV.css +1 -0
  78. temporal_agent_harness-0.3.0/temporal_agent_harness/ui/dist/index.html +13 -0
  79. temporal_agent_harness-0.3.0/temporal_agent_harness/ui/dist/temporal-logo.svg +3 -0
  80. temporal_agent_harness-0.3.0/temporal_agent_harness/utils/__init__.py +6 -0
  81. temporal_agent_harness-0.3.0/temporal_agent_harness/utils/large_payload.py +227 -0
  82. temporal_agent_harness-0.3.0/temporal_agent_harness/web/__init__.py +50 -0
  83. temporal_agent_harness-0.3.0/temporal_agent_harness/web/app.py +682 -0
  84. temporal_agent_harness-0.3.0/temporal_agent_harness/web/cli.py +173 -0
  85. temporal_agent_harness-0.3.0/temporal_agent_harness/web/client.py +87 -0
  86. temporal_agent_harness-0.3.0/temporal_agent_harness/web/registry.py +82 -0
  87. temporal_agent_harness-0.3.0/temporal_agent_harness/web/serve.py +66 -0
  88. temporal_agent_harness-0.3.0/temporal_agent_harness/web/session_manager.py +124 -0
  89. temporal_agent_harness-0.3.0/temporal_agent_harness/web/session_manager_worker.py +90 -0
  90. temporal_agent_harness-0.3.0/temporal_agent_harness/web/task_queue_status.py +158 -0
  91. temporal_agent_harness-0.3.0/temporal_agent_harness.egg-info/PKG-INFO +566 -0
  92. temporal_agent_harness-0.3.0/temporal_agent_harness.egg-info/SOURCES.txt +94 -0
  93. temporal_agent_harness-0.3.0/temporal_agent_harness.egg-info/dependency_links.txt +1 -0
  94. temporal_agent_harness-0.3.0/temporal_agent_harness.egg-info/entry_points.txt +2 -0
  95. temporal_agent_harness-0.3.0/temporal_agent_harness.egg-info/requires.txt +31 -0
  96. temporal_agent_harness-0.3.0/temporal_agent_harness.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Temporal Technologies Inc. All rights reserved.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,566 @@
1
+ Metadata-Version: 2.4
2
+ Name: temporal-agent-harness
3
+ Version: 0.3.0
4
+ Summary: Temporal-native agent harness (experimental) — durable agents on Temporal, with the AI SDKs you already use.
5
+ Author: Temporal Technologies Inc.
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/temporal-community/temporal-agent-harness
8
+ Project-URL: Repository, https://github.com/temporal-community/temporal-agent-harness
9
+ Project-URL: Issues, https://github.com/temporal-community/temporal-agent-harness/issues
10
+ Project-URL: Changelog, https://github.com/temporal-community/temporal-agent-harness/releases
11
+ Keywords: temporal,workflow,agents,harness,llm,ai
12
+ Classifier: Development Status :: 2 - Pre-Alpha
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: temporalio>=1.31.0
21
+ Requires-Dist: pydantic>=2.0
22
+ Provides-Extra: genai
23
+ Requires-Dist: google-genai>=2.0.0; extra == "genai"
24
+ Requires-Dist: google-auth>=2.53.0; extra == "genai"
25
+ Provides-Extra: s3
26
+ Requires-Dist: temporalio[aioboto3]>=1.31.0; extra == "s3"
27
+ Provides-Extra: openai-agents
28
+ Requires-Dist: openai-agents>=0.22.0; extra == "openai-agents"
29
+ Requires-Dist: mcp<3,>=2.1.1; extra == "openai-agents"
30
+ Requires-Dist: temporalio[opentelemetry]>=1.31.0; extra == "openai-agents"
31
+ Provides-Extra: pydantic-ai
32
+ Requires-Dist: pydantic-ai-slim[temporal]>=2.13; extra == "pydantic-ai"
33
+ Provides-Extra: ui
34
+ Requires-Dist: fastapi[standard]>=0.136.3; extra == "ui"
35
+ Provides-Extra: code-mode
36
+ Requires-Dist: pydantic-monty==0.0.18; extra == "code-mode"
37
+ Provides-Extra: nexus-mcp
38
+ Requires-Dist: temporal-nexus-mcp; python_version >= "3.13" and extra == "nexus-mcp"
39
+ Requires-Dist: openai-agents>=0.22.0; extra == "nexus-mcp"
40
+ Requires-Dist: mcp<3,>=2.1.1; extra == "nexus-mcp"
41
+ Requires-Dist: temporalio[opentelemetry]>=1.31.0; extra == "nexus-mcp"
42
+ Dynamic: license-file
43
+
44
+ # Temporal Agent Harness
45
+
46
+ **Build durable, composable AI agents with a rich tool-approval policy engine that can seamlessly elevate to a human with built-in human-in-the-loop.**
47
+
48
+ > ⚠️ **Experimental.** An early, fast-moving project from Temporal Technologies. APIs will change.
49
+
50
+ The Temporal Agent Harness gives your agents capabilities that are painful and error-prone to
51
+ build yourself:
52
+
53
+ - agents **survive crashes and resume mid-turn**, exactly where they left off (no wasted tokens!);
54
+ - a **rich tool-approval policy engine** decides exactly when a tool call needs human
55
+ sign-off — handing control to a human only then, and resuming the moment it's granted —
56
+ - agents **compose programmatically**, through real typed contracts (not limited to just text in,
57
+ text out);
58
+ - **Code Mode** — one tool that runs a Python script over your toolset — so a single
59
+ turn orchestrates many tool calls with real control flow and concurrency;
60
+ - **callback tools** let an agent invoke a tool that runs on the *client* — reading a file on a
61
+ user's laptop, capturing a photo on their phone — even though the agent runs on a remote worker;
62
+ - agents are **fully observable** — a standardized, full-lifecycle event stream lets you watch them
63
+ live or replay exactly what they did;
64
+
65
+ all while you write the actual turn logic with the **AI SDKs you already know**.
66
+
67
+ Every agent is a durable Temporal workflow at its core, giving agents access to the full breadth
68
+ of Temporal primitives. Tools as activities or workflow functions, every turn a streamed,
69
+ replayable history. The harness packages those primitives into a toolkit built for first-class
70
+ agent development, so you get the power without hand-rolling the orchestration.
71
+
72
+ ## Installation
73
+
74
+ There are two ways in, depending on what you want to do. Both pin to a **tagged release** —
75
+ see [Versioning and stability](#versioning-and-stability) for why that matters.
76
+
77
+ ### Try it — run the example agents
78
+
79
+ Clone at a release tag. No Node/pnpm needed: the browser UI ships prebuilt.
80
+
81
+ ```bash
82
+ git clone --branch 0.3.0 https://github.com/temporal-community/temporal-agent-harness.git
83
+ cd temporal-agent-harness
84
+ ```
85
+
86
+ Git will note that you're in "detached HEAD" — that's expected, it just means you're sitting on
87
+ the tag rather than on a branch. Later, move to a newer release with
88
+ `git fetch --tags && git checkout <version>`, or see what changed between two of them with
89
+ `git diff 0.1.0 0.2.0`.
90
+
91
+ Then jump to [Run the examples](#run-the-examples). You'll need
92
+ [uv](https://docs.astral.sh/uv/), [just](https://just.systems/), and the Temporal cli (or Temporal Cloud).
93
+
94
+ ### Build with it — add the harness to your own project
95
+
96
+ Add it as a **git dependency pinned to a tag**. In a [`uv`](https://docs.astral.sh/uv/)-managed
97
+ project:
98
+
99
+ ```bash
100
+ # core harness — define and run agent workflows
101
+ uv add "temporal-agent-harness @ git+https://github.com/temporal-community/temporal-agent-harness.git@0.3.0"
102
+ ```
103
+
104
+ Or declare it in `pyproject.toml` — depend on the package (with any extras you need) and point
105
+ its source at the tag:
106
+
107
+ ```toml
108
+ [project]
109
+ dependencies = [
110
+ "temporal-agent-harness[ui]",
111
+ ]
112
+
113
+ [tool.uv.sources]
114
+ temporal-agent-harness = { git = "https://github.com/temporal-community/temporal-agent-harness.git", tag = "0.3.0" }
115
+ ```
116
+
117
+ Then run `uv sync`.
118
+
119
+ **Extras:**
120
+
121
+ - **`ui`** — the reusable FastAPI server and packaged browser UI (pulls in `fastapi[standard]`,
122
+ including Uvicorn). The built Svelte assets are always in the artifact; only the server runtime
123
+ dependencies are gated behind this extra, so core agent-worker installs stay smaller.
124
+ - **`code-mode`** — for workers that host **Code Mode** agents; pulls in
125
+ [`pydantic-monty`](https://pypi.org/project/pydantic-monty/), the sandbox the scripts run in.
126
+ (The workflow-side `agent.code_mode_tool` factory itself needs nothing extra, so importing it
127
+ never requires this dependency.)
128
+
129
+ Combine extras in the dependency spec, e.g. `"temporal-agent-harness[ui,code-mode]"`.
130
+
131
+ Agent authors use the harness runtime from `temporal_agent_harness.harness`. Applications that
132
+ want the built-in session manager and UI use `temporal_agent_harness.web`:
133
+
134
+ ```python
135
+ from temporalio.client import Client
136
+ from temporalio.envconfig import ClientConfig
137
+
138
+ from temporal_agent_harness.plugin import AgentHarnessPlugin
139
+ from temporal_agent_harness.web import (
140
+ create_agent_harness_app,
141
+ create_session_manager_worker,
142
+ )
143
+
144
+
145
+ async def run_session_manager() -> None:
146
+ connect_config = ClientConfig.load_client_connect_config()
147
+ client = await Client.connect(**connect_config, plugins=[AgentHarnessPlugin()])
148
+ worker = create_session_manager_worker(client)
149
+ await worker.run()
150
+
151
+
152
+ app = create_agent_harness_app(registry_path="agents.toml")
153
+ ```
154
+
155
+ Then serve the app with Uvicorn:
156
+
157
+ ```bash
158
+ uvicorn my_app.web:app --host 0.0.0.0 --port 8000
159
+ ```
160
+
161
+ The registry lists the launchable agents the UI can create:
162
+
163
+ ```toml
164
+ [[agents]]
165
+ key = "my-agent"
166
+ workflow_type = "MyAgent"
167
+ task_queue = "my-agent-task-queue"
168
+ label = "My Agent"
169
+ description = "A short description shown in the UI."
170
+ ```
171
+
172
+ The app factory serves both `/api/*` and the packaged Svelte UI. The helper
173
+ `create_session_manager_worker` only registers the packaged session-manager
174
+ workflow; run your own agent workflows on their own workers and task queues.
175
+
176
+ ## Versioning and stability
177
+
178
+ **Install from a tagged release.** Every release is listed on the
179
+ [releases page](https://github.com/temporal-community/temporal-agent-harness/releases), and each
180
+ tag marks a commit that is known-good at the moment it was cut: the tests passed, and the
181
+ prebuilt browser UI matches the source it was built from.
182
+
183
+ **This project is very early and experimental - `main` may break at any time.**
184
+
185
+ Releases are cut manually, when the maintainers judge the state stable. This project is
186
+ **experimental** and pre-1.0: APIs will change between releases, without warning. Pinning to a
187
+ tag is what keeps that churn from reaching you unannounced — so pin, and upgrade deliberately
188
+ when you want to try out new harness capabilities.
189
+
190
+ ## What you get
191
+
192
+ ### 🛡️ Durable execution — the foundation
193
+ Every agent is a Temporal workflow, so durability isn't a feature you add, it's the ground you
194
+ build on. A worker can crash, redeploy, or restart mid-turn and the agent resumes precisely where
195
+ it was — no lost state, no double-run tool calls. Model and tool calls retry by policy; an agent
196
+ can wait minutes or days for an external event without holding a process open; and every turn,
197
+ tool call, and decision is recorded and replayable.
198
+
199
+ ### 📊 Standardized agents, fully observable
200
+ The interface to agents built on this harness is **carefully standardized**. Every agent takes the
201
+ same configuration contract and emits the same structured **event stream**: a protocol (under
202
+ active development) that captures an agent's entire lifecycle — every turn, model interaction, tool
203
+ call (start / end / error), reply token, citation, approval decision, subagent hand-off, and
204
+ token-usage tally.
205
+
206
+ That one standardized stream is the foundation of **observability and analytics** for every agent
207
+ you build. **Watch an agent live** as it works, or **replay exactly what happened** afterward —
208
+ what it decided, which tools it ran, what it cost, and where a human stepped in. You instrument
209
+ once; every agent on the harness gets it.
210
+
211
+ ### 🙋 Human-in-the-loop, solved
212
+ Tool approvals are built in and **safe-by-default**: any tool call can require human sign-off, and
213
+ a gated call **pauses inside the workflow and resumes durably** whenever a decision arrives (no
214
+ matter how long that takes) — there's no approval queue, state machine, or callback plumbing for
215
+ you to build. The policy engine is sophisticated out of the box: layered rules, inherently-safe
216
+ auto-approval, per-tool allow-lists, "approve and stop asking," per-session overrides, runtime
217
+ policy updates, and custom predicates.
218
+
219
+ ### 🔌 Bring your own AI SDK
220
+ Write turn logic with the SDK you already know. The harness's integrations turn each SDK call into
221
+ a durable Temporal activity — so retries and credentials never leak into your workflow code.
222
+ Support is growing across the Python AI SDKs and agent frameworks Temporal integrates with:
223
+
224
+ | AI SDK | Status | Notes |
225
+ | --- | --- | --- |
226
+ | [Google Gemini](temporal_agent_harness/ai_sdks/google_genai_plugin) | ✅ Available now | Ships in this repo and is **experimental** - [Python SDK](https://github.com/temporalio/sdk-python) has a fully-supported non-harness integration. |
227
+ | [OpenAI Agents SDK](temporal_agent_harness/ai_sdks/openai_agents) | ✅ Available now | Ships in this repo and is **experimental** - [Python SDK](https://github.com/temporalio/sdk-python) has a fully-supported non-harness integration. |
228
+ | [Pydantic AI](temporal_agent_harness/ai_sdks/pydantic_ai_harness.py) | ✅ Available now | Directly uses Pydantic's Temporal plugin |
229
+ | [Google ADK](https://adk.dev/integrations/temporal/) | 🟡 Planned | - |
230
+ | [Strands Agents](https://docs.temporal.io/develop/python/integrations/strands-agents) | 🟡 Planned | - |
231
+ | [LangGraph](https://docs.temporal.io/develop/python/integrations/langgraph) | 🟡 Planned | - |
232
+
233
+ ### 📡 Durable and inline tools
234
+ Tools come in two on-worker flavors — durable, activity-backed tools (`@agent.activity_tool_defn`)
235
+ that run as retried, observable Temporal activities, and inline workflow tools
236
+ (`@agent.tool_defn`). Each publishes its own start/end lifecycle events onto the agent's
237
+ standardized event stream. (A third flavor — **callback tools** — runs on an attached client
238
+ instead of the worker; see below.)
239
+
240
+ ### 📞 Callback tools — let the client run the tool
241
+ An agent running on a Temporal worker often needs to act somewhere it can't reach — a file on the
242
+ user's laptop, a photo from their phone, a device on a private network. A **callback tool**
243
+ (`@agent.callback_tool_defn`) has no worker-side body: the agent **pauses inside the workflow**,
244
+ publishes the call, and an **attached client executes it on its own machine** and sends the result
245
+ back. You declare only the tool's typed contract (its `...` body is enforced) — the harness
246
+ supplies the single generic implementation. Because it's dispatched like any other tool, a callback
247
+ tool inherits the *same* approval policy, `tool_start`/`tool_end` events, and durable pause/resume:
248
+ the workflow simply waits (seconds or days) until a result arrives, and that result is validated
249
+ against the tool's declared output type before the turn continues. See
250
+ [`examples/callback_tools/wiki_agent`](examples/callback_tools/wiki_agent) — a cloud-shaped agent
251
+ that organizes a Markdown wiki on *your* local disk through a thin terminal client.
252
+
253
+ ### 🧩 Agents that are more than chatbots
254
+ Most frameworks treat an agent as a single text-in / text-out function. Here, an agent exposes a
255
+ **strongly-typed interface**: declare named operations with `@agent.accepts` over pydantic models
256
+ (plain text is just one shape). The agent **advertises its own callable surface** — operation
257
+ names and input/output schemas — so it's self-describing and ready to be driven programmatically,
258
+ by your code or by another agent.
259
+
260
+ ### 🔗 Composable by construction
261
+ Because agents have typed, self-describing interfaces, any harness agent can become a
262
+ **strongly-typed toolset** another agent drives — start it, call its operations, stop it.
263
+ Multi-agent systems compose through real contracts, not strings pasted between prompts.
264
+
265
+ ### 🧑‍💻 Code Mode — give the model code, not just a call menu
266
+ Hand a model **one tool that runs a Python script** over your existing tools, instead of a long
267
+ menu of individual calls. `agent.code_mode_tool([...tools...])` turns any set of harness tools
268
+ into a single run-a-script tool: the model writes Python that calls them as async host functions,
269
+ with real control flow — loops, conditionals, `min`/`max`, and `asyncio.gather` concurrency — so
270
+ one turn orchestrates many calls. Each host call is still dispatched through the runner as a
271
+ durable, approval-gated, observable activity, and the script is statically type-checked against
272
+ your tools' signatures **before it runs**. And since a subagent toolset is just a list of tools,
273
+ Code Mode composes over subagents for free.
274
+
275
+
276
+ ## A taste
277
+
278
+ ```python
279
+ from datetime import timedelta
280
+
281
+ from pydantic import BaseModel
282
+ from temporalio import workflow
283
+ from temporalio.contrib.workflow_streams import WorkflowStream
284
+ from temporalio.workflow import ActivityConfig
285
+
286
+ from temporal_agent_harness.harness import AgentWorkflowRunner, agent, slash_commands
287
+ from temporal_agent_harness.harness.agent_protocol import AgentConfig, ToolApprovalPolicy
288
+
289
+
290
+ # A durable, activity-backed tool: runs as a retried, observable Temporal activity and
291
+ # publishes its own tool_start/tool_end events on the turn stream.
292
+ @agent.activity_tool_defn(
293
+ activity_config=ActivityConfig(start_to_close_timeout=timedelta(seconds=30)),
294
+ )
295
+ async def search_flights(origin: str, destination: str, date: str) -> str:
296
+ ...
297
+
298
+
299
+ # Strongly-typed messages — an agent operation is more than a string in and a string out.
300
+ class PlanTrip(BaseModel):
301
+ destination: str
302
+ nights: int
303
+
304
+
305
+ class Itinerary(BaseModel):
306
+ summary: str
307
+ total_usd: float
308
+
309
+
310
+ @agent.defn
311
+ class TravelAgent:
312
+ @workflow.init
313
+ def __init__(self, config: AgentConfig) -> None:
314
+ # Tool approvals are safe-by-default; here, auto-approve only tools that
315
+ # statically declare themselves inherently safe.
316
+ self._runner = AgentWorkflowRunner(
317
+ config,
318
+ stream=WorkflowStream(),
319
+ approval_policy_default=ToolApprovalPolicy.allow_inherently_safe(),
320
+ slash_commands=slash_commands.default_commands(),
321
+ )
322
+
323
+ @workflow.run
324
+ async def run(self, config: AgentConfig) -> None:
325
+ await self._runner.run(self)
326
+
327
+ # A typed, self-describing operation. The agent advertises this signature, so callers —
328
+ # your code or another agent — can drive it programmatically. Your turn logic goes here:
329
+ # call your AI SDK, run tools through the runner, and return the typed reply.
330
+ # (See examples/monty for a complete, model-in-the-loop agent on the Gemini integration.)
331
+ @agent.accepts
332
+ async def plan_trip(self, request: PlanTrip) -> Itinerary:
333
+ ...
334
+ ```
335
+
336
+ ## Running a worker — one plugin
337
+
338
+ An agent is a Temporal workflow, so it runs on a Temporal worker. `AgentHarnessPlugin` is the
339
+ single registration that wires that worker (and its client) for the harness — you never
340
+ assemble the harness's activity list or its data converter by hand:
341
+
342
+ ```python
343
+ from temporalio.client import Client
344
+ from temporalio.envconfig import ClientConfig
345
+ from temporalio.worker import Worker
346
+
347
+ from temporal_agent_harness.plugin import AgentHarnessPlugin
348
+
349
+ client = await Client.connect(
350
+ **ClientConfig.load_client_connect_config(),
351
+ # Your AI SDK's plugin first, the harness plugin last.
352
+ plugins=[OpenAIAgentsPlugin(model_params=...), AgentHarnessPlugin(tools=MY_TOOLS)],
353
+ )
354
+
355
+ worker = Worker(client, task_queue="my-agent", workflows=[TravelAgent])
356
+ await worker.run()
357
+ ```
358
+
359
+ The worker declares only its workflows. Adding the plugin brings:
360
+
361
+ - the harness's **data converter** (Pydantic + large-payload offload) — add the plugin to every
362
+ client, worker, and server in the deployment so they all agree on it;
363
+ - the durable **activity body** of each `@agent.activity_tool_defn` tool in `tools=` (pass your
364
+ whole toolset — tools with no worker-side body are skipped);
365
+ - the **subagent** and **Code Mode** activities.
366
+
367
+ Order it last, after any AI SDK's plugin, so that SDK's payload converter wins. Registering it
368
+ on the client is enough — Temporal applies a client's plugins to workers built from it.
369
+
370
+ ## Code Mode
371
+
372
+ Most agents call tools one at a time — a round-trip per call. **Code Mode** hands the model a
373
+ single tool that runs a Python *script* over your tools, so one turn can search, filter, branch,
374
+ and act across many calls with ordinary control flow and `asyncio.gather` concurrency.
375
+
376
+ `agent.code_mode_tool(tools, name=...)` takes any list of harness tools and returns one inline
377
+ tool. Its generated description tells the model the sandbox contract and every host function's
378
+ signature + result shape — derived from your tools, so you never hand-write or maintain it. Hand
379
+ the returned tool to your model's tool-calling loop like any other tool.
380
+
381
+ ```python
382
+ from temporal_agent_harness.harness import agent
383
+
384
+ # Any @agent.activity_tool_defn / @agent.tool_defn tools — including a subagent toolset.
385
+ run_code = agent.code_mode_tool(
386
+ [search_flights, search_hotels, book_flight, get_trip_summary],
387
+ name="run_travel_code",
388
+ )
389
+ # The model then writes, e.g., a script like this and calls run_travel_code with it:
390
+ #
391
+ # import asyncio
392
+ # async def main():
393
+ # flights, hotels = await asyncio.gather( # independent calls run concurrently
394
+ # search_flights({"origin": "SFO", "destination": "JFK", "date": "2026-07-01"}),
395
+ # search_hotels({"city": "New York", "check_in": "2026-07-01", "check_out": "2026-07-05"}),
396
+ # )
397
+ # cheapest = min(flights["flights"], key=lambda f: f["price_usd"])
398
+ # return await book_flight({"flight_id": cheapest["flight_id"], "passenger_name": "Ada Lovelace"})
399
+ # asyncio.run(main())
400
+ ```
401
+
402
+ - **Durable, gated, and observable per call.** The script runs in a sandbox; each host call is
403
+ dispatched back through the runner as its own durable activity — keeping that tool's approval
404
+ policy and `tool_start`/`tool_end` events. Writing the script is inert; only the host calls act.
405
+ - **Type-checked before it runs.** Code Mode generates static type-check stubs from your tools'
406
+ signatures, so a wrong argument or an unknown result key comes back as an error to fix rather
407
+ than a bad run.
408
+ - **Composes over subagents.** `agent.subagent_toolset(...)` returns a list of tools, so drop it
409
+ straight into `code_mode_tool([...])` — the model's script can drive subagents too.
410
+ - **Several per agent.** Give one agent multiple `code_mode_tool`s (distinct `name`s) over
411
+ disjoint or overlapping tool sets.
412
+
413
+ A worker that hosts a Code Mode agent needs the two sandbox-stepping activities and the durable
414
+ bodies of any activity-backed host tools. Both come from
415
+ [`AgentHarnessPlugin`](#running-a-worker--one-plugin) — the stepping activities as soon as the
416
+ `code-mode` extra (which pulls in [`pydantic-monty`](https://pypi.org/project/pydantic-monty/),
417
+ the sandbox the scripts run in) is installed:
418
+
419
+ ```python
420
+ client = await Client.connect(..., plugins=[AgentHarnessPlugin(tools=my_tools)])
421
+ worker = Worker(client, task_queue=..., workflows=[MyAgent])
422
+ ```
423
+
424
+ See [`examples/monty`](examples/monty) for three agents all built on Code Mode: a no-model script
425
+ runner, a conversational agent that writes its own scripts, and a subagent-driven variant.
426
+
427
+ ## Slash Commands
428
+
429
+ Agents can expose human/operator slash commands through a small library of
430
+ workflow-safe command definitions. A command bundles the UI metadata returned by
431
+ the `operator_interface` query with the deterministic handler that runs inside
432
+ the workflow.
433
+
434
+ If `slash_commands` is omitted, `AgentWorkflowRunner` enables the packaged
435
+ defaults:
436
+
437
+ | Command | Effect |
438
+ | --- | --- |
439
+ | `/approvals strict\|safe\|skip` | Change the live tool-approval policy. |
440
+ | `/allow-tools tool_name` | Auto-approve one or more named tools for this session. |
441
+ | `/status` | Show the current harness status. |
442
+ | `/stop` | Stop the agent workflow. |
443
+
444
+ Configure exactly the packaged commands you want in one place:
445
+
446
+ ```python
447
+ from temporal_agent_harness.harness import slash_commands
448
+
449
+ self._runner = AgentWorkflowRunner(
450
+ config,
451
+ stream=WorkflowStream(),
452
+ approval_policy_default=ToolApprovalPolicy.always_require_approvals(),
453
+ slash_commands=slash_commands.commands("approvals", "status", "stop"),
454
+ )
455
+ ```
456
+
457
+ Pass an empty list to disable packaged slash commands:
458
+
459
+ ```python
460
+ slash_commands=[]
461
+ ```
462
+
463
+ Custom commands use the same registry. For example, a model selector can share
464
+ one implementation across the first-class operator update path and the normal
465
+ `slash` turn path:
466
+
467
+ ```python
468
+ SUPPORTED_MODELS = ("gemini-3.5-flash", "gemini-3.1-flash-lite")
469
+
470
+ self._runner = AgentWorkflowRunner(
471
+ config,
472
+ stream=WorkflowStream(),
473
+ approval_policy_default=ToolApprovalPolicy.always_require_approvals(),
474
+ slash_commands=[
475
+ *slash_commands.default_commands(),
476
+ slash_commands.model_selector(
477
+ choices=SUPPORTED_MODELS,
478
+ set_model=lambda model: setattr(self, "_model", model),
479
+ description="Set the model for this session.",
480
+ ),
481
+ ],
482
+ )
483
+ ```
484
+
485
+ ## Requirements
486
+
487
+ - Python **3.11+**
488
+ - [uv](https://docs.astral.sh/uv/) for dependency management
489
+ - [just](https://just.systems/) for the example recipes
490
+ - A Temporal service. `just temporal` starts a local dev server if you have the `temporal`
491
+ CLI installed.
492
+
493
+ [pnpm](https://pnpm.io/) is **not** required to run anything: the browser UI ships prebuilt in
494
+ `temporal_agent_harness/ui/dist`, both in release archives and in the repo. You only need it to
495
+ *change* the UI — see [UI development](docs/internal/development.md#ui-development).
496
+
497
+ ## Run the examples
498
+
499
+ These run from a checkout or an unpacked
500
+ [release archive](#try-it--run-the-example-agents) — the steps are identical.
501
+
502
+ One `.env.local` at the **project root** serves every example. Create it once:
503
+
504
+ ```bash
505
+ cp .env.example .env.local
506
+ ```
507
+
508
+ Set the creds for whichever agents you'll run: `OPENAI_API_KEY` (react_agent, openai_hello,
509
+ pydantic_ai_hello) and/or `GEMINI_API_KEY` (monty, wiki, coding). The default committed
510
+ `temporal.local.toml` profile points at a local Temporal dev server.
511
+
512
+ ### One example, standalone
513
+
514
+ Each example runs on its own from its directory — [`examples/monty`](examples/monty) (a
515
+ conversational Code Mode travel agent + a subagent variant) is the best starting point:
516
+
517
+ ```bash
518
+ cd examples/monty
519
+ just temporal # local Temporal dev server; skip if you bring your own
520
+ just session-manager # worker hosting the packaged SessionManagerWorkflow
521
+ just server # serves the Svelte UI + /api on :8000 (this example's agents only)
522
+ just worker # this example's agent worker
523
+ ```
524
+
525
+ Open <http://localhost:8000> and pick an agent. Every example follows the same recipe set
526
+ (`temporal` / `session-manager` / `server` / `worker`, plus `client` where noted). `just server`
527
+ serves the prebuilt UI from `temporal_agent_harness/ui/dist`, so it needs no Node/pnpm. If you're
528
+ changing the UI, use `just dev-server` (rebuild + serve) instead.
529
+
530
+ ### All examples behind one UI
531
+
532
+ The **root** justfile runs every example agent at once so the UI lists them all. From the project root,
533
+ each in its own terminal:
534
+
535
+ ```bash
536
+ just temporal # start FRESH (or `just reset-manager` first — see the gotcha)
537
+ just session-manager # shared session-manager worker
538
+ just server # serves the MERGED registry (all agents) on http://localhost:8000
539
+ just workers # co-launch all six agent workers (Ctrl-C stops them; or run `just worker-<name>` each)
540
+ ```
541
+
542
+ Then create a session for any agent in the UI. A few need extra setup or a client:
543
+
544
+ | Agent | Needs |
545
+ |---|---|
546
+ | OpenAI Hello · Pydantic AI Hello | `OPENAI_API_KEY`; chat directly in the UI |
547
+ | Monty (both) | `GEMINI_API_KEY`; chat directly in the UI |
548
+ | ReAct Agent | `OPENAI_API_KEY`; the **F1 MCP server** at `F1_MCP_SERVER_HOME` ([setup](examples/react_agent/README.md#the-f1-mcp-server)); `just react-client` to answer its `ask_user` (chat alone works in the UI) |
549
+ | Wiki (callback) | `GEMINI_API_KEY`; **`just wiki-client --wiki-dir ./wiki`** — required, or its tool calls hang |
550
+ | Coding (callback) | `GEMINI_API_KEY`; **`just coding-shim <dir>`** + the OpenCode TUI — required |
551
+
552
+ **Gotcha — the session manager caches its registry.** The server seeds the `session-manager`
553
+ workflow with the registry on first start and reuses the existing one after that. So when you switch
554
+ between a single-example server and the all-agents server (or change the set), run
555
+ `just reset-manager` before the next `just server`, or start a fresh Temporal dev server. Also: an
556
+ agent whose worker isn't running will accept a created session but never progress (it parks) — start
557
+ its worker.
558
+
559
+ ## Status & docs
560
+
561
+ This is experimental and under active development; expect breaking changes — see
562
+ [Versioning and stability](#versioning-and-stability) for what that means for installs. Deeper
563
+ design documentation — the agent protocol, the streaming model, human-in-the-loop approvals, and
564
+ agents-as-subagents — lives under [`docs/internal/`](docs/internal). Contributor setup
565
+ (repository layout, the root `justfile`, UI development, and packaging) is in
566
+ [`docs/internal/development.md`](docs/internal/development.md).