@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.
- package/README.md +534 -5
- package/dist/src/harness/agent-protocol/agent-interface.d.ts +11 -0
- package/dist/src/harness/agent-protocol/agent-interface.js +14 -0
- package/dist/src/harness/agent-workflow/types.d.ts +3 -0
- package/dist/src/harness/agent-workflow.d.ts +12 -1
- package/dist/src/harness/agent-workflow.js +106 -35
- package/dist/src/harness/agent.d.ts +7 -0
- package/dist/src/harness/agent.js +8 -2
- package/package.json +6 -4
- package/skills/temporal-harness-typescript/SKILL.md +42 -0
- package/skills/temporal-harness-typescript/agents/openai.yaml +4 -0
- package/skills/temporal-harness-typescript/assets/starter/README.md.tmpl +95 -0
- package/skills/temporal-harness-typescript/assets/starter/agents.toml.tmpl +7 -0
- package/skills/temporal-harness-typescript/assets/starter/agents.ts.tmpl +61 -0
- package/skills/temporal-harness-typescript/assets/starter/env.example.tmpl +3 -0
- package/skills/temporal-harness-typescript/assets/starter/gitignore.tmpl +6 -0
- package/skills/temporal-harness-typescript/assets/starter/package.json.tmpl +35 -0
- package/skills/temporal-harness-typescript/assets/starter/tsconfig.json.tmpl +13 -0
- package/skills/temporal-harness-typescript/assets/starter/worker.ts.tmpl +24 -0
- package/skills/temporal-harness-typescript/references/advanced.md +57 -0
- package/skills/temporal-harness-typescript/references/api.md +110 -0
- package/skills/temporal-harness-typescript/references/authoring.md +65 -0
- package/skills/temporal-harness-typescript/references/verification.md +17 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Advanced TypeScript features
|
|
2
|
+
|
|
3
|
+
Read the installed package's public types and the agent's generated schema for exact signatures.
|
|
4
|
+
Prefer a small change to the working starter over copying internal runtime implementation.
|
|
5
|
+
|
|
6
|
+
- **Observable state:** declare state on the agent, initialize it through its schema, and mutate
|
|
7
|
+
through the provided state reference. Mutations publish patches; changing an unrelated local
|
|
8
|
+
object does not publish observable state. Keep mutation callbacks synchronous.
|
|
9
|
+
- **Approvals:** distinguish permission to run a tool from supplying a callback result. Preserve
|
|
10
|
+
the requesting agent and tool IDs, especially for subagents. See `api.md` for HTTP calls.
|
|
11
|
+
- **Callbacks:** the tool implementation runs on the client; model execution waits for a result.
|
|
12
|
+
Use the application client's callback handlers or pending callback interface.
|
|
13
|
+
- **Subagents:** expose typed handlers and compose through the harness subagent APIs. Follow
|
|
14
|
+
events using their agent identity rather than flattening the tree into the parent's transcript.
|
|
15
|
+
- **Code Mode:** requires the optional Monty sandbox dependency on the worker. Expose an explicit
|
|
16
|
+
toolset; scripts run against those host functions. Filesystem mounts and durable activity
|
|
17
|
+
filesystems have separate declarations and worker registrations.
|
|
18
|
+
- **Mid-turn handlers:** choose enqueue/accept/reject intentionally. Use `/api/messages` plus
|
|
19
|
+
`/api/attach` for clients that support messages joining an existing turn.
|
|
20
|
+
|
|
21
|
+
For complete worked examples matching a release, use the repository's release-tagged Monty and
|
|
22
|
+
OpenAI examples. A checkout is optional for normal application development. Do not assume Python
|
|
23
|
+
and TypeScript model integrations or advanced feature signatures are interchangeable.
|
|
24
|
+
|
|
25
|
+
## Add observable state to the starter
|
|
26
|
+
|
|
27
|
+
Import `state` from `@temporalio/agent-harness`. Add a declaration to `defineAgent` and read its
|
|
28
|
+
reference from the `create` context:
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
// Alongside handlers in defineAgent({...}):
|
|
32
|
+
states: { counter: state(z.object({ messages: z.number().int().default(0) })) },
|
|
33
|
+
|
|
34
|
+
// In create({ runner, state }), inside a handler:
|
|
35
|
+
state.counter.mutate(draft => { draft.messages += 1; });
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Read `state.counter.current` for the current value. Mutation callbacks must be synchronous.
|
|
39
|
+
Regenerate the agent schema; client codegen then exposes the `counter` state.
|
|
40
|
+
|
|
41
|
+
## Choose a tool boundary
|
|
42
|
+
|
|
43
|
+
| Work | Public declaration | Registration |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Deterministic, fast computation | `tool({ ..., execute })` | Adapt through the model integration |
|
|
46
|
+
| External I/O, database or provider calls | `activityTool({ ..., activityOptions })` | `new AgentHarnessPlugin({ tools: [toolActivity(declaration, implementation)] })` on the worker |
|
|
47
|
+
| Browser/user environment | `tool({ ..., outputSchema })` without `execute` | Supply the result through the application client |
|
|
48
|
+
|
|
49
|
+
Declarations come from `@temporalio/agent-harness`; `toolActivity` comes from
|
|
50
|
+
`@temporalio/agent-harness-worker`. Keep implementations with Node/network imports out of the
|
|
51
|
+
workflow bundle. Set timeouts and make side effects idempotent across retries. `inherentlySafe`
|
|
52
|
+
is an explicit approval-policy decision. Use `injected<T>()` bindings for workflow-supplied
|
|
53
|
+
values that should stay out of model input schemas.
|
|
54
|
+
|
|
55
|
+
`subagentToolset` and `codeModeTool` are exported from the core package. Code Mode also needs
|
|
56
|
+
`@pydantic/monty` on the worker. Inspect their installed declarations for lifecycle, mounting,
|
|
57
|
+
and toolset options. Keep each child's registry, worker queue, and generated interface aligned.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Harness API and session lifecycle
|
|
2
|
+
|
|
3
|
+
The shared Python server serves the same HTTP API and browser console for Python and TypeScript
|
|
4
|
+
agents. Run a Temporal service, session-manager worker, API server, and your agent worker with
|
|
5
|
+
the same connection and namespace. Worker task queues must match the registry.
|
|
6
|
+
|
|
7
|
+
## Discover and start
|
|
8
|
+
|
|
9
|
+
`GET /api/agents` lists registered workflow types, init-data schemas, and worker poller status.
|
|
10
|
+
A poller indicates availability on a queue, not proof that it hosts a particular workflow type.
|
|
11
|
+
`GET /api/sessions` lists sessions. Create one using the **workflow type**, not the registry key:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
curl --fail-with-body http://localhost:8000/api/sessions \
|
|
15
|
+
-H 'Content-Type: application/json' \
|
|
16
|
+
-d '{"agent_workflow_type":"OpenAIHelloAgentTs"}'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The response's `workflow_id` is the session ID used by subsequent calls. An optional `session_id`
|
|
20
|
+
lets an integration derive a stable ID from its own conversation identity; repeated creation
|
|
21
|
+
with the same ID is idempotent. Supply `data` when the agent requires init data. Do not send it
|
|
22
|
+
for an agent that takes none. The server checks presence and the agent validates its schema.
|
|
23
|
+
Read `GET /api/agent-interface/{session_id}` for handlers and their input/output schemas.
|
|
24
|
+
|
|
25
|
+
## Send and observe separately
|
|
26
|
+
|
|
27
|
+
Use `POST /api/messages` for typed messages:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"session_id": "the-workflow-id",
|
|
32
|
+
"message": {"type": "ask", "payload": {"text": "What's the weather in Paris?"}},
|
|
33
|
+
"request_id": "stable-id-for-this-one-delivery"
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
An accepted message may open a turn, join a running turn, or be queued. Acceptance is not
|
|
38
|
+
completion. Save the returned `message_id` and follow the event stream for its result. Reuse
|
|
39
|
+
`request_id` only when retrying the same delivery; distinct user messages need distinct IDs.
|
|
40
|
+
|
|
41
|
+
Attach with `GET /api/attach?session_id=...&from_offset=0` (URL-encode the ID). The response is
|
|
42
|
+
SSE (`text/event-stream`). Events carry JSON data, including `resume_offset` when available.
|
|
43
|
+
Save the last processed resume offset and pass it as `from_offset` on reconnect. Do not assume
|
|
44
|
+
browser `Last-Event-ID` is the resume mechanism. Read the event's handler/message/agent identity:
|
|
45
|
+
a session can contain queued messages, mid-turn handlers, and subagent events.
|
|
46
|
+
|
|
47
|
+
Use the published `@temporalio/agent-harness-client`, React, or Svelte clients when possible;
|
|
48
|
+
they implement stream/state handling. `POST /api/chat` is a convenience for a turn it opens;
|
|
49
|
+
use message submission plus attach for general application clients and mid-turn handlers.
|
|
50
|
+
|
|
51
|
+
## Human interaction
|
|
52
|
+
|
|
53
|
+
`POST /api/approve` takes `session_id`, `tool_id`, `approved`, optional `reason`, and optional
|
|
54
|
+
`remember`. An approval authorizes the pending tool; it does not provide a callback result.
|
|
55
|
+
|
|
56
|
+
`POST /api/callback-result` takes `session_id`, `tool_id`, and `result` or `error`. A callback
|
|
57
|
+
executes in the client environment. Preserve the identity of the agent requesting it; a UI may
|
|
58
|
+
be answering a subagent's request. The client SDK handles routing across the agent tree.
|
|
59
|
+
|
|
60
|
+
## State, status, and closing
|
|
61
|
+
|
|
62
|
+
Apply a state snapshot before its subsequent patches, in stream order. Use the SDK's state
|
|
63
|
+
implementation instead of treating every event as a complete state document.
|
|
64
|
+
|
|
65
|
+
- `GET /api/status/{session_id}`: current turn, pending work, and agent status.
|
|
66
|
+
- `GET /api/workflow-status/{workflow_id}`: execution status.
|
|
67
|
+
- `POST /api/sessions/{session_id}/close`: signal the agent to wind down and settle pending interactions.
|
|
68
|
+
|
|
69
|
+
Disconnecting an SSE stream does **not** close the agent. A network error is not evidence that
|
|
70
|
+
its workflow has ended. Reconnect; only treat an explicit not-found/closed result as terminal.
|
|
71
|
+
|
|
72
|
+
## Errors and schemas
|
|
73
|
+
|
|
74
|
+
Check HTTP status before parsing a success response. Invalid init-data presence returns 422;
|
|
75
|
+
other errors may contain `error`/`message` or FastAPI's `detail`. Do not retry a rejected payload
|
|
76
|
+
unchanged. On a transport failure, retry a submission using its original request ID.
|
|
77
|
+
|
|
78
|
+
The running server exposes `/openapi.json` and `/docs`, generated from its route declarations.
|
|
79
|
+
To save the HTTP contract while the server is running:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
curl --fail-with-body http://localhost:8000/openapi.json -o openapi.json
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Generate an agent's schema from its declaration:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Python
|
|
89
|
+
temporal-agent-harness schema my_agent.workflow:OpenAIHelloAgentWorkflow -o agent.schema.json
|
|
90
|
+
# TypeScript: compile first (agent-harness-schema is the same command)
|
|
91
|
+
agent-harness schema ./dist/agents.js#OpenAIHelloAgentTs -o agent.schema.json
|
|
92
|
+
# Either schema can generate a typed application client
|
|
93
|
+
harness-codegen agent.schema.json -o Agent.ts
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The code generator is distributed as `@temporalio/agent-harness-codegen`. Regenerate and commit
|
|
97
|
+
client types after changing handlers, state, or init data. Never patch generated types manually.
|
|
98
|
+
|
|
99
|
+
## Registry and deployment
|
|
100
|
+
|
|
101
|
+
`agents.toml` declares a key, workflow type, task queue, label, description, and optional schema
|
|
102
|
+
path relative to the TOML file. Keep the schema's agent name equal to `workflow_type`. The server
|
|
103
|
+
never imports your agent implementation. The session manager caches its registry: replacing the
|
|
104
|
+
registry file alone does not update an already-running manager's list. Restart with fresh local
|
|
105
|
+
Temporal state only when the development sessions are disposable.
|
|
106
|
+
|
|
107
|
+
The built-in local payload store is for processes sharing a host/filesystem. For a multi-host
|
|
108
|
+
deployment, configure a shared offload store consistently across clients, workers, and server.
|
|
109
|
+
Expose the API through your application's authentication boundary when deploying beyond local
|
|
110
|
+
development. Embedding the FastAPI app lets you add your own middleware and routes.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# TypeScript authoring
|
|
2
|
+
|
|
3
|
+
Use `@temporalio/agent-harness` in workflow modules and `@temporalio/agent-harness-worker` on the
|
|
4
|
+
Node worker. `npx agent-harness init ./my-agent` materializes a runnable OpenAI starter. The same
|
|
5
|
+
starter is installed with this skill under `assets/starter/`; read `agents.ts.tmpl` and
|
|
6
|
+
`worker.ts.tmpl` there for the complete wiring. Its exact Temporal SDK versions match the model
|
|
7
|
+
plugin; keep those versions aligned. Harness package versions are separate from the underlying
|
|
8
|
+
Temporal SDK versions. Compile ESM before starting.
|
|
9
|
+
|
|
10
|
+
A minimal agent, with a safe inline tool and a model-free handler:
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import { TextMessage, TextReply, ToolApprovalPolicy, defineAgent, handler, tool } from "@temporalio/agent-harness";
|
|
15
|
+
|
|
16
|
+
export const getWeather = tool({
|
|
17
|
+
name: "get_weather",
|
|
18
|
+
description: "Return sample weather for a city.",
|
|
19
|
+
inputSchema: z.object({ city: z.string() }),
|
|
20
|
+
outputSchema: z.string(),
|
|
21
|
+
inherentlySafe: true,
|
|
22
|
+
execute: async ({ city }) => `It's 72°F and sunny in ${city}.`,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const HelloAgent = defineAgent({
|
|
26
|
+
name: "HelloAgent",
|
|
27
|
+
approvalPolicyDefault: ToolApprovalPolicy.allowInherentlySafe(),
|
|
28
|
+
handlers: {
|
|
29
|
+
ping: handler({ description: "Echo without a model call.", input: TextMessage, output: TextReply }),
|
|
30
|
+
},
|
|
31
|
+
create: ({ runner }) => ({
|
|
32
|
+
async ping({ text }) {
|
|
33
|
+
return { text };
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The starter's `ask` handler adds the model turn: it passes `getWeather` through
|
|
40
|
+
`asOpenAIAgentTool(runner, ...)` and awaits `runStreamed(runner, sdkAgent, input, ...)`.
|
|
41
|
+
|
|
42
|
+
Public authoring imports come from `@temporalio/agent-harness`: `defineAgent`, `handler`, `tool`,
|
|
43
|
+
`state`, `TextMessage`, `TextReply`, and `ToolApprovalPolicy`. Use Zod schemas for inputs and
|
|
44
|
+
outputs. `defineAgent({ name, handlers, create })` returns the workflow export; `create` receives
|
|
45
|
+
its runner, init data, and declared state references. Keep Node imports and external I/O in the
|
|
46
|
+
worker or activities, not in the workflow module.
|
|
47
|
+
|
|
48
|
+
Use `ToolApprovalPolicy.allowInherentlySafe()` for tools explicitly marked `inherentlySafe`.
|
|
49
|
+
For OpenAI, import `asOpenAIAgentTool` and `runStreamed` from
|
|
50
|
+
`@temporalio/agent-harness/ai-sdks/openai-agents-harness`. Consume the stream and await completion.
|
|
51
|
+
Keep conversation history inside the agent instance. The worker's `OpenAIAgentsPlugin` comes from
|
|
52
|
+
`@temporalio/agent-harness-worker/ai-sdks/openai-agents`, followed by `AgentHarnessPlugin`.
|
|
53
|
+
Activity tool implementations are registered using `toolActivity` on the harness plugin.
|
|
54
|
+
|
|
55
|
+
Point `workflowsPath` at the compiled agent module. Generate its schema with
|
|
56
|
+
`agent-harness schema ./dist/agents.js#Export -o Agent.schema.json` (the `agent-harness-schema`
|
|
57
|
+
binary is the same command). The registry's `workflow_type`
|
|
58
|
+
must match `defineAgent`'s `name`, and its queue must match the worker. The schema CLI does not
|
|
59
|
+
start Temporal. The shared Python server reads the schema without importing TypeScript code.
|
|
60
|
+
|
|
61
|
+
The shared services come from the Python package: the starter's `npm run harness:dev` runs
|
|
62
|
+
`temporal-agent-harness dev agents.toml` through `uvx`, beside Temporal and the Node worker.
|
|
63
|
+
`npx agent-harness doctor --schema Agent.schema.json` checks the installed package, schema, API,
|
|
64
|
+
and agent worker availability. For direct Temporal connection and local TOML validation, use the
|
|
65
|
+
shared Python CLI's `doctor --registry agents.toml`. Both support `--json` and never reset services.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Verification and troubleshooting
|
|
2
|
+
|
|
3
|
+
Validate against the installed SDK version. Run the starter's schema command before launching
|
|
4
|
+
services. A successful check includes discovering the agent, creating a session, sending a typed
|
|
5
|
+
message, seeing a tool result and completed reply, and reconnecting to the same session.
|
|
6
|
+
Use a scripted model for repeatable automated tests; do not claim a scripted response is a live
|
|
7
|
+
provider test. Test approval/callback paths separately when your agent uses them.
|
|
8
|
+
|
|
9
|
+
If the console loads but cannot start a session, check the session-manager worker. If a session
|
|
10
|
+
exists but messages stall, check the agent worker, task queue, and namespace. If a model fails,
|
|
11
|
+
check the worker's provider credentials and optional integration dependencies. If registration
|
|
12
|
+
changes appear stale, inspect the session manager's cached registry before replacing local state.
|
|
13
|
+
|
|
14
|
+
Keep the Temporal server, namespace, and task queues isolated for tests. Do not terminate unrelated
|
|
15
|
+
sessions or reset a user's existing development server to make a test pass. Temporal workflow code
|
|
16
|
+
must remain deterministic; move external I/O into activities. Replaying a completed activity uses
|
|
17
|
+
its recorded result, but retryable activities with side effects still need idempotent design.
|