@temporalio/agent-harness 0.2.0 → 0.3.0

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 (23) hide show
  1. package/README.md +534 -5
  2. package/dist/src/harness/agent-protocol/agent-interface.d.ts +11 -0
  3. package/dist/src/harness/agent-protocol/agent-interface.js +14 -0
  4. package/dist/src/harness/agent-workflow/types.d.ts +3 -0
  5. package/dist/src/harness/agent-workflow.d.ts +12 -1
  6. package/dist/src/harness/agent-workflow.js +106 -35
  7. package/dist/src/harness/agent.d.ts +7 -0
  8. package/dist/src/harness/agent.js +8 -2
  9. package/package.json +6 -4
  10. package/skills/temporal-harness-typescript/SKILL.md +42 -0
  11. package/skills/temporal-harness-typescript/agents/openai.yaml +4 -0
  12. package/skills/temporal-harness-typescript/assets/starter/README.md.tmpl +95 -0
  13. package/skills/temporal-harness-typescript/assets/starter/agents.toml.tmpl +7 -0
  14. package/skills/temporal-harness-typescript/assets/starter/agents.ts.tmpl +61 -0
  15. package/skills/temporal-harness-typescript/assets/starter/env.example.tmpl +3 -0
  16. package/skills/temporal-harness-typescript/assets/starter/gitignore.tmpl +6 -0
  17. package/skills/temporal-harness-typescript/assets/starter/package.json.tmpl +35 -0
  18. package/skills/temporal-harness-typescript/assets/starter/tsconfig.json.tmpl +13 -0
  19. package/skills/temporal-harness-typescript/assets/starter/worker.ts.tmpl +24 -0
  20. package/skills/temporal-harness-typescript/references/advanced.md +57 -0
  21. package/skills/temporal-harness-typescript/references/api.md +110 -0
  22. package/skills/temporal-harness-typescript/references/authoring.md +65 -0
  23. package/skills/temporal-harness-typescript/references/verification.md +17 -0
package/README.md CHANGED
@@ -1,6 +1,535 @@
1
- # @temporalio/agent-harness
1
+ # Temporal Agent Harness for TypeScript
2
2
 
3
- The workflow-safe authoring API of the Temporal agent harness: `defineAgent`, handlers, tools,
4
- approvals, state and the protocol, with no Node dependencies. Agents written with it run on a
5
- worker from [`@temporalio/agent-harness-worker`](../worker/README.md), whose README also shows how
6
- to serve them to the harness's server and console.
3
+ **Temporal-native agent harness (experimental) — build durable, composable AI agents on Temporal,
4
+ with the AI SDKs you already use.**
5
+
6
+ Your agents survive worker restarts, expose typed message handlers, stream model and tool events,
7
+ and pause for human approval. Compose them as subagents, give them Code Mode tools, and build
8
+ products over their live state with the React and Svelte clients.
9
+
10
+ > **Experimental.** APIs change between releases. Pin versions and upgrade deliberately.
11
+
12
+ [Python quickstart](https://github.com/temporal-community/temporal-agent-harness/blob/main/python/README.md#quickstart)
13
+ · [Examples](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/examples)
14
+ · [Releases](https://github.com/temporal-community/temporal-agent-harness/releases)
15
+
16
+ <a id="coding-agent-setup-and-starter"></a>
17
+
18
+ ## Quickstart
19
+
20
+ Create an agent, install its coding-agent skill, and open a working session in four steps.
21
+ No repository checkout or Bazel installation is needed.
22
+
23
+ > **Available with the next npm release.** The `agent-harness` command is not in the published
24
+ > 0.2.0 package, and the starter's `harness:dev` script needs the Python release that adds
25
+ > `temporal-agent-harness dev`. Once both are published, the `@latest` command below selects
26
+ > them. Until then, use the [manual setup](#build-with-it--install-from-npm) with published packages.
27
+
28
+ You need **Node.js 22.18+**, npm, [uv](https://docs.astral.sh/uv/getting-started/installation/),
29
+ the [Temporal CLI](https://docs.temporal.io/cli), and an OpenAI API key for the starter's `ask`
30
+ handler. `uvx` can provision Python for the shared server, which requires Python 3.11+.
31
+
32
+ ### 1. Create the project
33
+
34
+ Run this from the directory where you want the new project:
35
+
36
+ ```bash
37
+ npx -y -p @temporalio/agent-harness-worker@latest agent-harness init my-agent
38
+ cd my-agent
39
+ npm install
40
+ ```
41
+
42
+ `npx` supplies the CLI before you have a project; the first run downloads the worker package and
43
+ the Temporal SDK's native bridge once. `init` creates a new directory containing `agents.ts`,
44
+ `worker.ts`, `agents.toml`, TypeScript configuration, a `package.json` named after the
45
+ directory, and a README. It pins the installed harness version, compatible Temporal/model
46
+ dependencies, and the Python package that provides the shared services, and refuses to overwrite
47
+ existing files. The starter's weather tool returns sample data.
48
+
49
+ ### 2. Configure it and install the skill
50
+
51
+ ```bash
52
+ cp env.example .env.local
53
+ npm run schema
54
+ npm run harness:skill # Codex reads .agents/skills
55
+ npx agent-harness skills install --for claude # Claude Code reads .claude/skills
56
+ ```
57
+
58
+ `.env.local` is where `OPENAI_API_KEY` goes once you try `ask`; never commit it. `schema`
59
+ compiles the agent and writes `agent.schema.json`. `harness:skill` copies this SDK release's skill, with
60
+ authoring, API, advanced-feature, and verification references, into the directory your coding
61
+ agent reads; run it for each tool you use. Installing the npm package bundles the skill; this
62
+ explicit step makes it discoverable in your project. For a tool with another skill directory,
63
+ run `npx agent-harness skills path` to locate the bundled resources, or use
64
+ `npx agent-harness skills install --dest <directory>`.
65
+
66
+ ### 3. Start the three services
67
+
68
+ Run each command in its own terminal, all from this project directory:
69
+
70
+ **Terminal 1 — Temporal service** (skip if your configured Temporal service is already running):
71
+
72
+ ```bash
73
+ temporal server start-dev
74
+ ```
75
+
76
+ **Terminal 2 — shared session manager, API, and harness console:**
77
+
78
+ ```bash
79
+ npm run harness:dev
80
+ ```
81
+
82
+ **Terminal 3 — your TypeScript agent worker:**
83
+
84
+ ```bash
85
+ npm start
86
+ ```
87
+
88
+ **The API, console, and session manager come from the Python package.** `harness:dev` runs
89
+ `temporal-agent-harness dev` through `uvx`, in one process and without a Python project of your
90
+ own; the Python version is pinned in `package.json`. Your agent worker runs in Node. The console
91
+ is prebuilt, so no frontend build is needed.
92
+
93
+ Defaults are `localhost:7233` and namespace `default`. For another Temporal service, export the
94
+ same `TEMPORAL_*` connection settings in each service terminal. `.env.local` loads only into the
95
+ Node worker. Leave `TASK_QUEUE_PREFIX` unset for this starter. Keep all three processes running.
96
+
97
+ ### 4. Verify, then develop with your coding agent
98
+
99
+ 1. Open the **harness console at <http://localhost:8000>**. Create a session for
100
+ **OpenAI Hello (TypeScript)**.
101
+ 2. Send `ping` a text message. Expect the same text back without a model request or an API key.
102
+ 3. Set `OPENAI_API_KEY` in `.env.local`, restart the worker, then send `ask`
103
+ **“What's the weather in Paris?”**. Expect a streamed reply and a completed `get_weather`
104
+ tool trace with sample weather data. This step calls OpenAI.
105
+ 4. Reload the page and confirm the session reconnects.
106
+ 5. Check startup and worker availability:
107
+
108
+ ```bash
109
+ npm run harness:doctor
110
+ ```
111
+
112
+ Temporal's own workflow UI is at <http://localhost:8233>; it is separate from the harness console.
113
+
114
+ With the services running, open the project in your coding agent. Reload its session if it was
115
+ already running, then ask it to use the skill:
116
+
117
+ | Tool | Prompt |
118
+ | --- | --- |
119
+ | Codex | `Use $temporal-harness-typescript to help me build this agent. Explain the starter, then verify it with ping before making model calls.` |
120
+ | Claude Code | `/temporal-harness-typescript Explain the starter, then verify it with ping before making model calls.` |
121
+
122
+ Edit `agents.ts` to change your agent and `worker.ts` for worker configuration. After changes,
123
+ stop the worker, run `npm run schema`, and restart `npm start`. Use a new session for changed
124
+ workflow behavior. Commit `agent.schema.json` and `package-lock.json`.
125
+
126
+ After upgrading the SDK, rerun the `skills install` commands to update unchanged skill files.
127
+ Local edits produce a conflict and are preserved. Application-client guidance is in the installed
128
+ skill's `references/api.md`.
129
+
130
+ See [coding-agent development](https://github.com/temporal-community/temporal-agent-harness/blob/main/docs/agent-development.md)
131
+ for skill discovery and updates, and [troubleshooting](#troubleshooting) if a service fails.
132
+
133
+ <a id="installation"></a>
134
+
135
+ ## Installation and manual reference
136
+
137
+ For a new application, start with the [four-step quickstart](#quickstart) above.
138
+ The reference below covers existing projects, manual setup, and repository examples.
139
+
140
+ | I want to… | Do this |
141
+ | --- | --- |
142
+ | **See it work** — chat with a TypeScript agent in a browser | [Try the example agent](#try-the-example-agent) from a release checkout |
143
+ | **Create a new agent project** with a coding-agent skill | [Four-step quickstart](#quickstart) |
144
+ | **Add the SDK to an existing project** or assemble it manually | [Install from npm](#build-with-it--install-from-npm) |
145
+
146
+ Your agent and worker are TypeScript. The shared console and session manager currently come
147
+ from the Python package, which `uvx` runs without a Python project of your own. Both SDKs use
148
+ the same console, registry format, and session API.
149
+
150
+ ### Which package?
151
+
152
+ | Package | Use it for |
153
+ | --- | --- |
154
+ | `@temporalio/agent-harness` | Define agents, handlers, tools, approvals, and state in workflow code |
155
+ | `@temporalio/agent-harness-worker` | Run agents: worker plugins, model activities, the server-side agent client, and schema CLI |
156
+ | `@temporalio/agent-harness-client` | Build an application client without a UI framework |
157
+ | `@temporalio/agent-harness-react` / `@temporalio/agent-harness-svelte` | Build a product UI with live, typed sessions; each re-exports the client |
158
+
159
+ Model integrations are opt-in imports and optional peer dependencies. The walkthrough below
160
+ installs the OpenAI Agents integration. The
161
+ [Monty example](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/examples/monty)
162
+ shows Gemini and Code Mode.
163
+
164
+ ### Try the example agent
165
+
166
+ You need [just](https://just.systems/), [Bazelisk](https://github.com/bazelbuild/bazelisk) installed
167
+ as `bazel`, and the [Temporal CLI](https://docs.temporal.io/cli). Bazel supplies the build
168
+ toolchains and dependencies. Examples live in the repository and are not shipped in npm packages.
169
+
170
+ ```bash
171
+ git clone --branch npm-v0.3.0 https://github.com/temporal-community/temporal-agent-harness.git
172
+ cd temporal-agent-harness
173
+ [ -f .env.local ] || cp .env.example .env.local
174
+ # Set OPENAI_API_KEY in .env.local.
175
+ cd typescript/examples/openai_hello
176
+ ```
177
+
178
+ Run each command in its own terminal, from that example directory:
179
+
180
+ ```bash
181
+ just temporal # skip if your configured Temporal server is already running
182
+ just session-manager
183
+ just server
184
+ just worker
185
+ ```
186
+
187
+ Open <http://localhost:8000> and follow [Your first session](#your-first-session).
188
+ The [example README](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/examples/openai_hello#readme)
189
+ explains the files and how to modify the agent. The checkout's recipes load the root `.env.local`.
190
+
191
+ ### Build with it — install from npm
192
+
193
+ You need Node.js **22.18 or later**, npm, an OpenAI API key, the
194
+ [Temporal CLI](https://docs.temporal.io/cli), and [uv](https://docs.astral.sh/uv/getting-started/installation/).
195
+ `uvx` can provision Python for the shared services; the Python harness requires Python 3.11+.
196
+
197
+ Create an empty directory:
198
+
199
+ ```bash
200
+ mkdir my-agent
201
+ cd my-agent
202
+ ```
203
+
204
+ Create `package.json`. The install commands below add its dependencies:
205
+
206
+ ```json
207
+ {
208
+ "name": "my-agent",
209
+ "private": true,
210
+ "type": "module",
211
+ "scripts": {
212
+ "build": "tsc -p tsconfig.json",
213
+ "schema": "npm run build && agent-harness-schema ./dist/agents.js#OpenAIHelloAgentTs -o OpenAIHelloAgentTs.schema.json",
214
+ "start": "node --env-file=.env.local dist/worker.js"
215
+ }
216
+ }
217
+ ```
218
+
219
+ Install the harness and the model integration:
220
+
221
+ ```bash
222
+ npm install --save-exact @temporalio/agent-harness@0.3.0 @temporalio/agent-harness-worker@0.3.0 @temporalio/agent-harness-client@0.3.0
223
+ npm install --save-exact @temporalio/activity@1.24.0 @temporalio/client@1.24.0 @temporalio/common@1.24.0 @temporalio/envconfig@1.24.0 @temporalio/plugin@1.24.0 @temporalio/worker@1.24.0 @temporalio/workflow@1.24.0 @temporalio/workflow-streams@1.24.0 @temporalio/openai-agents@1.24.0
224
+ npm install --save-exact @openai/agents-core@0.14.3 @openai/agents-openai@0.14.3 openai@6.46.0 zod@4.6.5
225
+ npm install --save-dev --save-exact typescript@7.0.2 @types/node@22.20.4
226
+ ```
227
+
228
+ The Temporal SDK packages above share **1.24.0**, matching the OpenAI plugin. The harness packages
229
+ have their own version, **0.2.0**. Pinning the Temporal dependencies together avoids installing
230
+ incompatible copies through the plugin. Commit `package-lock.json` and use `npm ci` on subsequent
231
+ installs. The shared Python services below are pinned separately to **0.8.0**.
232
+
233
+ Create `tsconfig.json`:
234
+
235
+ ```json
236
+ {
237
+ "compilerOptions": {
238
+ "target": "ES2023",
239
+ "module": "NodeNext",
240
+ "moduleResolution": "NodeNext",
241
+ "strict": true,
242
+ "skipLibCheck": true,
243
+ "types": ["node"],
244
+ "rootDir": ".",
245
+ "outDir": "dist"
246
+ },
247
+ "include": ["agents.ts", "worker.ts"]
248
+ }
249
+ ```
250
+
251
+ Create `.gitignore`:
252
+
253
+ ```gitignore
254
+ node_modules/
255
+ dist/
256
+ .env.local
257
+ ```
258
+
259
+ Create `.env.local` with your API key:
260
+
261
+ ```dotenv
262
+ OPENAI_API_KEY=your-openai-api-key
263
+ ```
264
+
265
+ `npm start` explicitly loads this file. The `uvx` commands below do not load it; they do not need
266
+ the OpenAI key. For a non-default Temporal connection, export the same connection settings in
267
+ all three service terminals as described under [Temporal connection](#temporal-connection).
268
+
269
+ ### Your first agent
270
+
271
+ Create `agents.ts`. This agent remembers the conversation across messages, calls one tool, and
272
+ streams the reply. The weather tool returns **sample data**, not a live weather forecast.
273
+
274
+ ```ts
275
+ import { Agent, type AgentInputItem } from "@openai/agents-core";
276
+ import { z } from "zod";
277
+ import {
278
+ TextMessage,
279
+ TextReply,
280
+ ToolApprovalPolicy,
281
+ defineAgent,
282
+ handler,
283
+ tool,
284
+ } from "@temporalio/agent-harness";
285
+ import {
286
+ asOpenAIAgentTool,
287
+ runStreamed,
288
+ } from "@temporalio/agent-harness/ai-sdks/openai-agents-harness";
289
+
290
+ const getWeather = tool({
291
+ name: "get_weather",
292
+ description: "Return sample weather for a city.",
293
+ inputSchema: z.object({ city: z.string() }),
294
+ outputSchema: z.string(),
295
+ inherentlySafe: true,
296
+ execute: async ({ city }) => `It's 72°F and sunny in ${city}.`,
297
+ });
298
+
299
+ export const OpenAIHelloAgentTs = defineAgent({
300
+ name: "OpenAIHelloAgentTs",
301
+ approvalPolicyDefault: ToolApprovalPolicy.allowInherentlySafe(),
302
+ handlers: {
303
+ ask: handler({
304
+ description: "Chat with the assistant. Ask about the weather to try its tool.",
305
+ input: TextMessage,
306
+ output: TextReply,
307
+ }),
308
+ },
309
+ create: ({ runner }) => {
310
+ let conversation: AgentInputItem[] = [];
311
+ return {
312
+ async ask({ text }) {
313
+ const sdkAgent = new Agent({
314
+ name: "Hello",
315
+ instructions: "Answer briefly. For weather questions, call get_weather and explain that it returns sample data.",
316
+ model: "gpt-5.1",
317
+ tools: [asOpenAIAgentTool(runner, getWeather)],
318
+ });
319
+ const input: AgentInputItem[] = [...conversation, { role: "user", content: text }];
320
+ const result = await runStreamed(runner, sdkAgent, input, {
321
+ defaultModelParams: {
322
+ startToCloseTimeout: "2 minutes",
323
+ heartbeatTimeout: "30 seconds",
324
+ },
325
+ });
326
+ for await (const _event of result) void _event;
327
+ await result.completed;
328
+ conversation = result.history;
329
+ return { text: String(result.finalOutput) };
330
+ },
331
+ };
332
+ },
333
+ });
334
+ ```
335
+
336
+ ### Running a worker — one harness plugin
337
+
338
+ Create `worker.ts`. The model integration's plugin comes first, then `AgentHarnessPlugin`.
339
+ The worker bundles the **compiled** `dist/agents.js`; it does not run the agent as ordinary
340
+ Node application code.
341
+
342
+ ```ts
343
+ import { fileURLToPath } from "node:url";
344
+ import { loadClientConnectConfig } from "@temporalio/envconfig";
345
+ import { NativeConnection, Worker } from "@temporalio/worker";
346
+ import { AgentHarnessPlugin } from "@temporalio/agent-harness-worker";
347
+ import { OpenAIAgentsPlugin } from "@temporalio/agent-harness-worker/ai-sdks/openai-agents";
348
+
349
+ // `ping` needs no model, so the worker starts without a key; only `ask` fails without one.
350
+ if (!process.env.OPENAI_API_KEY)
351
+ console.warn("OPENAI_API_KEY is not set: `ping` works, `ask` will fail until it is (see env.example)");
352
+
353
+ const { connectionOptions, namespace } = loadClientConnectConfig();
354
+ const connection = await NativeConnection.connect(connectionOptions);
355
+ try {
356
+ const worker = await Worker.create({
357
+ connection,
358
+ namespace: namespace ?? "default",
359
+ taskQueue: "openai-hello-ts",
360
+ workflowsPath: fileURLToPath(new URL("./agents.js", import.meta.url)),
361
+ plugins: [new OpenAIAgentsPlugin(), new AgentHarnessPlugin()],
362
+ });
363
+ await worker.run();
364
+ } finally {
365
+ await connection.close();
366
+ }
367
+ ```
368
+
369
+ The [worker reference](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/packages/worker#readme)
370
+ covers activity tools, plugins, schema generation, and connection configuration.
371
+
372
+ ### Register the agent
373
+
374
+ Create `agents.toml` in the project root:
375
+
376
+ ```toml
377
+ [[agents]]
378
+ key = "openai-hello-ts"
379
+ workflow_type = "OpenAIHelloAgentTs"
380
+ task_queue = "openai-hello-ts"
381
+ label = "OpenAI Hello (TypeScript)"
382
+ description = "Chat with a TypeScript agent and try its sample weather tool."
383
+ schema = "OpenAIHelloAgentTs.schema.json"
384
+ ```
385
+
386
+ Build the project and generate the schema:
387
+
388
+ ```bash
389
+ npm run schema
390
+ ```
391
+
392
+ The server reads this document without importing your agent code. It describes handlers, state,
393
+ and init data; agents with required init data get a form before session creation. Regenerate it
394
+ when those declarations change and commit the generated schema with `agents.toml`.
395
+
396
+ ### Run the web UI
397
+
398
+ Run one command per terminal, all from `my-agent/`:
399
+
400
+ ```bash
401
+ # 1. Temporal dev server. Skip if your configured server is already running.
402
+ # Temporal's own workflow UI is at http://localhost:8233.
403
+ temporal server start-dev
404
+
405
+ # 2. Session-manager worker: starts agents and tracks sessions.
406
+ uvx --from 'temporal-agent-harness[ui]==0.9.0' temporal-agent-harness session-manager
407
+
408
+ # 3. Harness console + JSON API: http://localhost:8000.
409
+ uvx --from 'temporal-agent-harness[ui]==0.9.0' temporal-agent-harness serve ./agents.toml
410
+
411
+ # 4. Your TypeScript agent worker; loads .env.local.
412
+ npm start
413
+ ```
414
+
415
+ The console is prebuilt in the Python package. No frontend build is needed. The console and
416
+ session-manager worker are shared infrastructure; your agent code runs on your TypeScript worker.
417
+ From the next Python release, `temporal-agent-harness dev ./agents.toml` runs steps 2 and 3 in
418
+ one process, as the quickstart's `npm run harness:dev` does.
419
+ Leave all four processes running while using the console. After editing TypeScript, stop your
420
+ worker, rerun `npm run schema`, and start it again. Start a new session when changing agent behavior.
421
+
422
+ ### Your first session
423
+
424
+ 1. Open the **harness console at <http://localhost:8000>**.
425
+ 2. Click **New session** (the **+** in the top bar), choose **OpenAI Hello (TypeScript)**,
426
+ then click **Start session**.
427
+ 3. Send **“What's the weather in Paris?”** to the `ask` handler.
428
+ 4. Watch the streamed reply, then expand **Ran 1 tool** in the chat to inspect `get_weather`
429
+ and its `{ "city": "Paris" }` input and sample result. The tool returns “It's 72°F and sunny in Paris.”; the model's wording can vary.
430
+ 5. Ask a follow-up in the same session. Reload the page to reconnect to its recorded conversation.
431
+
432
+ ![Harness console showing a TypeScript weather conversation and its completed get_weather tool call](https://raw.githubusercontent.com/temporal-community/temporal-agent-harness/main/docs/images/typescript-quickstart.png)
433
+
434
+ The screenshot shows the console after a completed weather turn. It uses a scripted model
435
+ response for a reproducible capture; the agent, tool execution, Temporal workflows, and console
436
+ are real. The quickstart above uses OpenAI and streams its response live.
437
+
438
+ ### Temporal connection
439
+
440
+ With no connection configured, all three services use `localhost:7233` and namespace `default`.
441
+ For a project profile or Temporal Cloud, run these exports in **each** service terminal before
442
+ starting the session manager, console, and TypeScript worker:
443
+
444
+ ```bash
445
+ export TEMPORAL_CONFIG_FILE="$PWD/temporal.toml"
446
+ export TEMPORAL_PROFILE=cloud
447
+ ```
448
+
449
+ Use a profile containing your connection details. A `temporal.toml` in the current directory is
450
+ not picked up automatically. All services must use the same address and namespace. This
451
+ walkthrough uses the literal `openai-hello-ts` task queue in both worker and registry; leave
452
+ `TASK_QUEUE_PREFIX` unset. See the worker reference if you need prefixed queues.
453
+
454
+ ## A taste — typed operations and init data
455
+
456
+ Like Python's `TravelAgent`, an agent can expose a typed operation rather than only text chat.
457
+ Zod schemas describe both its messages and the data supplied when the session starts:
458
+
459
+ ```ts
460
+ import { z } from "zod";
461
+ import { defineAgent, handler, ToolApprovalPolicy } from "@temporalio/agent-harness";
462
+
463
+ export const TravelAgent = defineAgent({
464
+ name: "TravelAgent",
465
+ approvalPolicyDefault: ToolApprovalPolicy.allowInherentlySafe(),
466
+ initData: {
467
+ schema: z.object({ name: z.string(), home_airport: z.string() }),
468
+ required: true,
469
+ },
470
+ handlers: {
471
+ plan_trip: handler({
472
+ description: "Draft an itinerary for the traveler.",
473
+ input: z.object({ destination: z.string(), nights: z.number().int().positive() }),
474
+ output: z.object({ summary: z.string(), total_usd: z.number() }),
475
+ }),
476
+ },
477
+ create: ({ data }) => ({
478
+ async plan_trip({ destination, nights }) {
479
+ // Replace this sample response with your model and tool calls.
480
+ return {
481
+ summary: `${data.name}: ${nights} nights in ${destination}, departing ${data.home_airport}.`,
482
+ total_usd: 0,
483
+ };
484
+ },
485
+ }),
486
+ });
487
+ ```
488
+
489
+ This is a separate authoring example, not needed to run Hello. To serve it, export it from your
490
+ workflow module, add a matching registry entry, and generate its schema using the worker CLI.
491
+
492
+ ## Troubleshooting
493
+
494
+ | Symptom | Check |
495
+ | --- | --- |
496
+ | The worker warns that `OPENAI_API_KEY` is not set, or the model reports an authentication error | `ping` works without a key; for `ask`, put a valid key in `.env.local` and use `npm start` from the project root. Restart the worker after changing it. |
497
+ | Cannot connect to Temporal | Start the dev server, or check `TEMPORAL_CONFIG_FILE`, `TEMPORAL_PROFILE`, and `TEMPORAL_*` overrides in every terminal. |
498
+ | Console loads but agent discovery or session creation stalls | Start the session-manager worker and confirm it uses the same Temporal address and namespace as the console. |
499
+ | A session starts but messages never progress | Start the TypeScript worker. Its task queue must match `agents.toml`, and its address and namespace must match the other services. |
500
+ | `dist/worker.js` or the schema file is missing | Run `npm run schema` from the project root before starting the services. |
501
+ | Duplicate protobuf registrations or incompatible Temporal types | Reinstall with the pinned dependency versions above. Keep the underlying Temporal SDK packages aligned with the OpenAI plugin. |
502
+ | An edited registry still shows the old agents | The session-manager workflow caches its registry. For disposable local development, restart a fresh in-memory Temporal dev server, then restart the services. This discards local sessions; use it only when you can discard them. |
503
+ | Port 8000 shows nothing useful | Open the harness console on 8000; Temporal's workflow UI on 8233 is a separate application. Check the `serve` terminal for startup errors. |
504
+
505
+ ## What next?
506
+
507
+ - **Approvals:** `allowInherentlySafe()` automatically permits this sample's read-only tool.
508
+ Use `alwaysRequireHumanApproval()` to try the console's approval flow in a new session.
509
+ See [human-in-the-loop approvals](https://github.com/temporal-community/temporal-agent-harness/blob/main/docs/internal/human-in-the-loop-tool-approvals.md)
510
+ for the shared concepts; its code examples are Python.
511
+ - **State:** declare observable state with `state(...)` and read or update it through the agent's
512
+ state references. See the
513
+ [Monty trip board](https://github.com/temporal-community/temporal-agent-harness/blob/main/typescript/examples/monty/trip-board.ts)
514
+ and its [agent declaration](https://github.com/temporal-community/temporal-agent-harness/blob/main/typescript/examples/monty/conversational-workflow.ts)
515
+ for a complete example.
516
+ - **Code Mode and subagents:** follow the
517
+ [TypeScript Monty example](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/examples/monty).
518
+ - **Your own product UI:** use the
519
+ [React](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/packages/react#readme)
520
+ or [Svelte](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/packages/svelte#readme)
521
+ client. These connect to the shared session API for agents written in either language.
522
+ - **Worker configuration:** see the
523
+ [worker package](https://github.com/temporal-community/temporal-agent-harness/tree/main/typescript/packages/worker#readme)
524
+ for activity registration and schema CLI details.
525
+
526
+ ## Versioning and stability
527
+
528
+ Pin the harness packages to the same exact harness release, and pin the underlying Temporal SDK
529
+ packages to the version required by your model integration. The Python services have a separate
530
+ release version. The manual walkthrough pairs npm **0.2.0** with Python **0.8.0**; do not assume those
531
+ version numbers should match.
532
+
533
+ Examples are not included in the npm packages: use the corresponding `npm-v<version>` repository
534
+ tag. Review the [release notes](https://github.com/temporal-community/temporal-agent-harness/releases)
535
+ when upgrading, regenerate schemas, and test the worker and shared services together.
@@ -19,6 +19,14 @@ export declare const MessageDisposition: {
19
19
  readonly QUEUED: "queued";
20
20
  };
21
21
  export type MessageDisposition = (typeof MessageDisposition)[keyof typeof MessageDisposition];
22
+ /** Why an agent closed: on the `agent_closed` event and `AgentStatus.close_reason`. */
23
+ export declare const CloseReason: {
24
+ /** A `close` signal arrived: from a client, an operator, or a parent's `stopSubagent`. */
25
+ readonly REQUESTED: "requested";
26
+ /** The agent sat idle for its whole `close_after_idle` and the runner closed it. */
27
+ readonly IDLE_TIMEOUT_EXPIRED: "idle_timeout_expired";
28
+ };
29
+ export type CloseReason = (typeof CloseReason)[keyof typeof CloseReason];
22
30
  /** What an auto-mode evaluator decides, named after AI SDK 7's tool-approval outcomes. */
23
31
  export declare const ApprovalVerdict: {
24
32
  readonly APPROVED: "approved";
@@ -271,6 +279,7 @@ export declare const AgentConfigSchema: z.ZodObject<{
271
279
  escalate_if_irreversible_above: number | null;
272
280
  } | null | undefined>>;
273
281
  agent_id: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodString>>, z.ZodTransform<string | null, string | null | undefined>>;
282
+ close_after_idle: z.ZodPipe<z.ZodOptional<z.ZodNullable<z.ZodNumber>>, z.ZodTransform<number | null, number | null | undefined>>;
274
283
  }, z.core.$strip>;
275
284
  export type AgentConfig = z.output<typeof AgentConfigSchema>;
276
285
  export type AgentConfigInput = z.input<typeof AgentConfigSchema>;
@@ -357,6 +366,8 @@ export interface AgentStatus {
357
366
  subagents: SubagentInfo[];
358
367
  approval_policy: ToolApprovalPolicy;
359
368
  has_auto_approval_evaluator: boolean;
369
+ /** Why the agent closed, or null while it is open. */
370
+ close_reason: CloseReason | null;
360
371
  }
361
372
  /** One turn of the current run (a continued run keeps its last 20), as the `agent_state` query indexes it. */
362
373
  export interface AcceptedFunction {
@@ -14,6 +14,13 @@ export const AGENT_ID_LENGTH = 6;
14
14
  export const APPROVAL_SHORT_ID_LENGTH = 6;
15
15
  export const MidTurn = { ENQUEUE: "enqueue", REJECT: "reject", ACCEPT: "accept" };
16
16
  export const MessageDisposition = { OPENED: "opened", JOINED: "joined", QUEUED: "queued" };
17
+ /** Why an agent closed: on the `agent_closed` event and `AgentStatus.close_reason`. */
18
+ export const CloseReason = {
19
+ /** A `close` signal arrived: from a client, an operator, or a parent's `stopSubagent`. */
20
+ REQUESTED: "requested",
21
+ /** The agent sat idle for its whole `close_after_idle` and the runner closed it. */
22
+ IDLE_TIMEOUT_EXPIRED: "idle_timeout_expired",
23
+ };
17
24
  /** What an auto-mode evaluator decides, named after AI SDK 7's tool-approval outcomes. */
18
25
  export const ApprovalVerdict = {
19
26
  APPROVED: "approved",
@@ -185,6 +192,13 @@ export const AgentConfigSchema = z.object({
185
192
  .regex(AGENT_ID_PATTERN)
186
193
  .nullish()
187
194
  .transform((v) => v ?? null),
195
+ // Seconds idle before the runner closes the agent; 0 never closes. Null leaves the agent's
196
+ // default (`closeAfterIdleDefault`) in force.
197
+ close_after_idle: z
198
+ .number()
199
+ .nonnegative()
200
+ .nullish()
201
+ .transform((v) => v ?? null),
188
202
  });
189
203
  // Not z.record, which rebuilds the object and drops a "__proto__" key.
190
204
  export const messagePayload = z
@@ -1,3 +1,4 @@
1
+ import type { Duration } from "@temporalio/common";
1
2
  import type { z } from "zod";
2
3
  import type { AgentMessage, AutoApprovalCriteria, AutoModeEvaluator, MessageContext, MidTurn, Schema, ToolApprovalPolicy } from "../agent-protocol/index.ts";
3
4
  import type { StateDecl } from "../state/index.ts";
@@ -60,6 +61,8 @@ export interface RunnerOptions {
60
61
  approvalPolicyDefault: ToolApprovalPolicy;
61
62
  autoApprovalCriteriaDefault?: AutoApprovalCriteria | undefined;
62
63
  autoModeEvaluator?: AutoModeEvaluator | undefined;
64
+ /** Close the agent once it has been idle this long; omitted or 0 never closes on idle. */
65
+ closeAfterIdleDefault?: Duration | undefined;
63
66
  states?: Readonly<Record<string, AnyStateDecl>> | undefined;
64
67
  /** The agent's validated init data, handed to each state's `initial`. */
65
68
  data?: unknown;
@@ -51,6 +51,13 @@ export declare class AgentWorkflowRunner {
51
51
  /** Declared state: each snapshot was published at construction, then every committed mutation. */
52
52
  readonly state: Readonly<Record<string, StateRef<Record<string, unknown>>>>;
53
53
  run(impls: Readonly<Record<string, HandlerImpl<HandlerDecl>>>): Promise<void>;
54
+ /**
55
+ * @internal The last step of a run, after teardown: parked stream polls are handed the last
56
+ * events, then every handler finishes. Not before teardown, which may publish too, and whose
57
+ * whole duration the stream must stay readable for: a poll arriving once pollers are detached
58
+ * is refused.
59
+ */
60
+ finish(): Promise<void>;
54
61
  /** @internal Enforce the approval policy for the in-flight tool call. Throws ToolApprovalDenied. */
55
62
  applyApprovalPolicy(toolId: string, toolName: string, toolInput: Record<string, unknown>, opts: {
56
63
  inherentlySafe: boolean;
@@ -66,7 +73,11 @@ export declare class AgentWorkflowRunner {
66
73
  outputSchema: Record<string, unknown>;
67
74
  timeout: string | number | undefined;
68
75
  }): Promise<unknown>;
69
- /** Start a child agent; returns its short, tree-unique handle (also its agent_id). */
76
+ /**
77
+ * Start a child agent; returns its short, tree-unique handle (also its agent_id). The child never
78
+ * closes on idle: `close_after_idle` is always pushed down as 0, overriding both `config` and the
79
+ * child's own default.
80
+ */
70
81
  startSubagent(agentKey: string, workflowType: string, taskQueue: string, { config, data }?: {
71
82
  config?: Partial<AgentConfig>;
72
83
  data?: unknown;