@minpeter/pss-runtime 0.1.0-next.0 → 0.1.0-next.2
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 +223 -191
- package/dist/agent-child-runs.js +16 -0
- package/dist/agent-child-runs.js.map +1 -0
- package/dist/agent-host-capabilities.js +9 -0
- package/dist/agent-host-capabilities.js.map +1 -0
- package/dist/agent-host-session-store.js +12 -0
- package/dist/agent-host-session-store.js.map +1 -0
- package/dist/agent-loop.js +59 -35
- package/dist/agent-loop.js.map +1 -1
- package/dist/agent-namespace.js +24 -0
- package/dist/agent-namespace.js.map +1 -0
- package/dist/agent-options.d.ts +35 -0
- package/dist/agent-options.js +16 -0
- package/dist/agent-options.js.map +1 -0
- package/dist/agent-resume.js +143 -0
- package/dist/agent-resume.js.map +1 -0
- package/dist/agent-session-entry.d.ts +13 -0
- package/dist/agent-validation.js +35 -0
- package/dist/agent-validation.js.map +1 -0
- package/dist/agent.d.ts +8 -33
- package/dist/agent.js +131 -55
- package/dist/agent.js.map +1 -1
- package/dist/child-session-cleanups.js +61 -0
- package/dist/child-session-cleanups.js.map +1 -0
- package/dist/execution/host.js +14 -0
- package/dist/execution/host.js.map +1 -0
- package/dist/execution/index.d.ts +4 -0
- package/dist/execution/index.js +3 -0
- package/dist/execution/memory-notifications.js +54 -0
- package/dist/execution/memory-notifications.js.map +1 -0
- package/dist/execution/memory-state.js +34 -0
- package/dist/execution/memory-state.js.map +1 -0
- package/dist/execution/memory-store.js +203 -0
- package/dist/execution/memory-store.js.map +1 -0
- package/dist/execution/memory.d.ts +7 -0
- package/dist/execution/memory.js +28 -0
- package/dist/execution/memory.js.map +1 -0
- package/dist/execution/run.js +55 -0
- package/dist/execution/run.js.map +1 -0
- package/dist/execution/types.d.ts +155 -0
- package/dist/index.d.ts +9 -10
- package/dist/index.js +1 -6
- package/dist/llm-tool-execution.d.ts +35 -0
- package/dist/llm-tool-execution.js +126 -0
- package/dist/llm-tool-execution.js.map +1 -0
- package/dist/llm.d.ts +11 -15
- package/dist/llm.js +5 -9
- package/dist/llm.js.map +1 -1
- package/dist/plugins.d.ts +20 -0
- package/dist/plugins.js +14 -0
- package/dist/plugins.js.map +1 -0
- package/dist/session/events.d.ts +26 -20
- package/dist/session/input-normalization.js +66 -0
- package/dist/session/input-normalization.js.map +1 -0
- package/dist/session/input.d.ts +0 -4
- package/dist/session/mapping.js +1 -2
- package/dist/session/mapping.js.map +1 -1
- package/dist/session/run.js +1 -0
- package/dist/session/run.js.map +1 -1
- package/dist/session/runtime-input.js +20 -58
- package/dist/session/runtime-input.js.map +1 -1
- package/dist/session/session-errors.js +18 -0
- package/dist/session/session-errors.js.map +1 -0
- package/dist/session/session-events.js +59 -0
- package/dist/session/session-events.js.map +1 -0
- package/dist/session/session-execution.js +88 -0
- package/dist/session/session-execution.js.map +1 -0
- package/dist/session/session-kill.js +23 -0
- package/dist/session/session-kill.js.map +1 -0
- package/dist/session/session-notification.js +58 -0
- package/dist/session/session-notification.js.map +1 -0
- package/dist/session/session-runtime-drain.js +22 -0
- package/dist/session/session-runtime-drain.js.map +1 -0
- package/dist/session/session-state.js +102 -0
- package/dist/session/session-state.js.map +1 -0
- package/dist/session/session-turn-error.js +35 -0
- package/dist/session/session-turn-error.js.map +1 -0
- package/dist/session/session-turn-processor.js +135 -0
- package/dist/session/session-turn-processor.js.map +1 -0
- package/dist/session/session.js +125 -335
- package/dist/session/session.js.map +1 -1
- package/dist/session/snapshot.js +5 -31
- package/dist/session/snapshot.js.map +1 -1
- package/dist/session/store/file.d.ts +1 -0
- package/dist/session/store/file.js +14 -0
- package/dist/session/store/file.js.map +1 -1
- package/dist/session/store/memory.d.ts +1 -0
- package/dist/session/store/memory.js +5 -0
- package/dist/session/store/memory.js.map +1 -1
- package/dist/session/store/types.d.ts +1 -0
- package/dist/subagent-background-child-run-state.js +51 -0
- package/dist/subagent-background-child-run-state.js.map +1 -0
- package/dist/subagent-background-child-run.js +103 -0
- package/dist/subagent-background-child-run.js.map +1 -0
- package/dist/subagent-background-in-process.js +98 -0
- package/dist/subagent-background-in-process.js.map +1 -0
- package/dist/subagent-background-notification-inbox.js +106 -0
- package/dist/subagent-background-notification-inbox.js.map +1 -0
- package/dist/subagent-background-notify.js +136 -0
- package/dist/subagent-background-notify.js.map +1 -0
- package/dist/subagent-background-resume-group.js +99 -0
- package/dist/subagent-background-resume-group.js.map +1 -0
- package/dist/subagent-background-runner.js +115 -0
- package/dist/subagent-background-runner.js.map +1 -0
- package/dist/subagent-background-schedule.js +43 -0
- package/dist/subagent-background-schedule.js.map +1 -0
- package/dist/subagent-child-run.js +68 -0
- package/dist/subagent-child-run.js.map +1 -0
- package/dist/subagent-job-cancel.js +84 -0
- package/dist/subagent-job-cancel.js.map +1 -0
- package/dist/subagent-job-observer.js +19 -0
- package/dist/subagent-job-observer.js.map +1 -0
- package/dist/subagent-job-output.js +87 -0
- package/dist/subagent-job-output.js.map +1 -0
- package/dist/subagent-job-state.js +66 -0
- package/dist/subagent-job-state.js.map +1 -0
- package/dist/subagent-jobs.js +96 -0
- package/dist/subagent-jobs.js.map +1 -0
- package/dist/subagent-prompt-schema.js +114 -0
- package/dist/subagent-prompt-schema.js.map +1 -0
- package/dist/subagent-run.js +111 -0
- package/dist/subagent-run.js.map +1 -0
- package/dist/subagents.js +125 -0
- package/dist/subagents.js.map +1 -0
- package/package.json +11 -6
- package/dist/plugins/compaction.d.ts +0 -15
- package/dist/plugins/compaction.js +0 -98
- package/dist/plugins/compaction.js.map +0 -1
- package/dist/plugins/index.d.ts +0 -5
- package/dist/plugins/index.js +0 -5
- package/dist/plugins/memory.d.ts +0 -11
- package/dist/plugins/memory.js +0 -146
- package/dist/plugins/memory.js.map +0 -1
- package/dist/plugins/runner.d.ts +0 -1
- package/dist/plugins/runner.js +0 -83
- package/dist/plugins/runner.js.map +0 -1
- package/dist/plugins/scope.js +0 -13
- package/dist/plugins/scope.js.map +0 -1
- package/dist/plugins/sessions.d.ts +0 -12
- package/dist/plugins/sessions.js +0 -34
- package/dist/plugins/sessions.js.map +0 -1
- package/dist/plugins/tool-hook-handlers.js +0 -77
- package/dist/plugins/tool-hook-handlers.js.map +0 -1
- package/dist/plugins/tool-hook-results.js +0 -64
- package/dist/plugins/tool-hook-results.js.map +0 -1
- package/dist/plugins/tool-hooks.js +0 -111
- package/dist/plugins/tool-hooks.js.map +0 -1
- package/dist/plugins/types.d.ts +0 -105
- package/dist/plugins/types.js +0 -20
- package/dist/plugins/types.js.map +0 -1
- package/dist/session/lifecycle.d.ts +0 -12
- package/dist/session/lifecycle.js +0 -126
- package/dist/session/lifecycle.js.map +0 -1
- package/dist/session/overlay-anchor.js +0 -151
- package/dist/session/overlay-anchor.js.map +0 -1
- package/dist/session/overlay.js +0 -141
- package/dist/session/overlay.js.map +0 -1
- package/dist/session/snapshot.d.ts +0 -1
- /package/dist/{agent-loop.d.ts → session/history.d.ts} +0 -0
- /package/dist/session/{runtime-input.d.ts → session-execution.d.ts} +0 -0
- /package/dist/{plugins/scope.d.ts → session/session-state.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -10,12 +10,31 @@ Minimal, platform-agnostic agent runtime with keyed sessions, synchronized
|
|
|
10
10
|
## Core DX
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
|
+
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
|
13
14
|
import { Agent } from "@minpeter/pss-runtime";
|
|
14
|
-
import {
|
|
15
|
+
import { createEnv } from "@t3-oss/env-core";
|
|
16
|
+
import { config as loadEnv } from "dotenv";
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
loadEnv({ path: ".env", quiet: true, override: true });
|
|
20
|
+
const env = createEnv({
|
|
21
|
+
runtimeEnv: process.env,
|
|
22
|
+
server: {
|
|
23
|
+
AI_API_KEY: z.string().trim().min(1),
|
|
24
|
+
AI_BASE_URL: z.url().trim().default("https://apis.opengateway.ai/v1"),
|
|
25
|
+
AI_MODEL: z.string().trim().min(1).default("minimax/MiniMax-M2.7"),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
15
28
|
|
|
16
|
-
const
|
|
29
|
+
const provider = createOpenAICompatible({
|
|
30
|
+
name: "custom",
|
|
31
|
+
apiKey: env.AI_API_KEY,
|
|
32
|
+
baseURL: env.AI_BASE_URL,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const agent = new Agent({
|
|
17
36
|
instructions: "Answer briefly.",
|
|
18
|
-
model:
|
|
37
|
+
model: provider(env.AI_MODEL),
|
|
19
38
|
});
|
|
20
39
|
|
|
21
40
|
const run = await agent.send("Hello");
|
|
@@ -28,22 +47,23 @@ for await (const event of run.events()) {
|
|
|
28
47
|
boundaries until the events consumer asks for the next event, so callers must
|
|
29
48
|
consume the events for the run to progress. This is what lets code react to
|
|
30
49
|
`turn-start`, `step-start`, and `step-end` before the next model snapshot is
|
|
31
|
-
created.
|
|
32
|
-
|
|
33
|
-
|
|
50
|
+
created.
|
|
51
|
+
|
|
52
|
+
`model` is the single public constructor key for model execution. Pass an AI SDK
|
|
53
|
+
`LanguageModel` for the managed runtime path with `instructions`, `tools`, and
|
|
54
|
+
`subagents`, or pass a custom `RuntimeLlm` function when you want to own the model
|
|
55
|
+
adapter yourself:
|
|
34
56
|
|
|
35
57
|
```ts
|
|
36
|
-
|
|
37
|
-
const session = agent.session("default");
|
|
58
|
+
import { Agent, type RuntimeLlm } from "@minpeter/pss-runtime";
|
|
38
59
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
60
|
+
const runtimeModel: RuntimeLlm = async ({ history }) => [
|
|
61
|
+
{ role: "assistant", content: `Seen ${history.length} messages.` },
|
|
62
|
+
];
|
|
42
63
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
64
|
+
const agent = new Agent({
|
|
65
|
+
model: runtimeModel,
|
|
66
|
+
});
|
|
47
67
|
```
|
|
48
68
|
|
|
49
69
|
Per-key conversations use `session(key)`:
|
|
@@ -96,17 +116,95 @@ The public transcript protocol is `AgentEvent`: live runs emit runtime-defined
|
|
|
96
116
|
events through `run.events()`. Provider/model message history is internal
|
|
97
117
|
continuation state, not a public history API.
|
|
98
118
|
|
|
99
|
-
##
|
|
119
|
+
## Subagents
|
|
120
|
+
|
|
121
|
+
Compose specialist agents by constructing them first and passing them as an
|
|
122
|
+
array. Top-level agents may omit metadata, but agents used as subagents need a
|
|
123
|
+
stable `name` and `description` so the runtime can expose clear model-facing
|
|
124
|
+
delegate tools.
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const researcher = new Agent({
|
|
128
|
+
name: "researcher",
|
|
129
|
+
description: "Researches facts and returns concise evidence.",
|
|
130
|
+
model,
|
|
131
|
+
instructions: "Research facts and return concise evidence.",
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const coordinator = new Agent({
|
|
135
|
+
model,
|
|
136
|
+
instructions: "Coordinate work and delegate when useful.",
|
|
137
|
+
subagents: [researcher],
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
For each subagent, the parent model receives a generated
|
|
142
|
+
`delegate_to_<name>` tool. The tool accepts `prompt`, optional `description`,
|
|
143
|
+
optional `sessionKey` suffix, and `run_in_background`. A provided `sessionKey`
|
|
144
|
+
is always scoped under the parent session and subagent name; the model cannot
|
|
145
|
+
select an arbitrary child session key. Omitting `run_in_background` defaults to
|
|
146
|
+
blocking behavior and returns compact child text, not the full child event
|
|
147
|
+
stream.
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
delegate_to_researcher({
|
|
151
|
+
prompt: "Find the current release notes and summarize the evidence.",
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
When the model sets `run_in_background: true`, the parent run can finish while
|
|
156
|
+
the child keeps working. The launch result includes a `bg_...` `task_id` and
|
|
157
|
+
tells the model to wait for a `<system-reminder>` before retrieving output.
|
|
158
|
+
When background work finishes, the session is notified with compact runtime
|
|
159
|
+
input. Hosts that support durable background work should resume the parent session
|
|
160
|
+
through the notification inbox and drain the returned `AgentRun`.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
delegate_to_researcher({
|
|
164
|
+
prompt: "Compare the API designs.",
|
|
165
|
+
run_in_background: true,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// After the completion reminder arrives:
|
|
169
|
+
background_output({ task_id: "bg_...", block: true });
|
|
170
|
+
background_cancel({ task_id: "bg_..." });
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The parent model context stays compact by default: completion reminders include
|
|
174
|
+
the task id, subagent name, description, and retrieval instruction. Full child
|
|
175
|
+
traces are not injected into the parent transcript by default. Background jobs
|
|
176
|
+
run in task-scoped child sessions. In-memory job handles are cleaned up after
|
|
177
|
+
completed output retrieval, while durable hosts keep compact completed output
|
|
178
|
+
available through the execution store for reconstructed agents. Background jobs
|
|
179
|
+
launched during the same parent turn share an internal completion barrier:
|
|
180
|
+
successful jobs notify once when the group settles, while failed jobs notify
|
|
181
|
+
immediately.
|
|
182
|
+
|
|
183
|
+
## Send, Host Resume, and Steer
|
|
100
184
|
|
|
101
185
|
Use `session.send(input)` for a new user turn. If a run is already active, the
|
|
102
|
-
turn is queued until the active run finishes. Use `session.steer(input)` when
|
|
103
|
-
input should steer the active run; if no run is active, it starts a normal
|
|
186
|
+
turn is queued until the active run finishes. Use `session.steer(input)` when
|
|
187
|
+
the input should steer the active run; if no run is active, it starts a normal
|
|
188
|
+
run.
|
|
189
|
+
|
|
190
|
+
Durable hosts resume completed background work by writing a notification record
|
|
191
|
+
and calling `agent.resume(notificationRunId)`. The resume call claims the
|
|
192
|
+
notification idempotently through its durable run id and returns one `AgentRun`,
|
|
193
|
+
or `null` when a duplicate queue/alarm delivery already claimed it.
|
|
194
|
+
|
|
195
|
+
Runtime-originated input is delivered through the host notification inbox and
|
|
196
|
+
internal plugin paths. App code should use `session.send()`, `session.steer()`,
|
|
197
|
+
or `agent.resume(runId)` for host-scheduled durable work.
|
|
104
198
|
|
|
105
|
-
|
|
199
|
+
Each accepted call returns one `AgentRun`. Drain that run's `events()` stream to
|
|
200
|
+
observe the turn; each `AgentRun.events()` stream is single-consumer.
|
|
201
|
+
|
|
202
|
+
Input APIs accept the same input shapes: strings, arrays of strings,
|
|
106
203
|
`{ type: "user-text", text }`, and multipart `{ type: "user-message", content }`
|
|
107
|
-
values. Active steering
|
|
108
|
-
runtime/API-originated input mapped internally to the model's
|
|
109
|
-
distinct from human-origin `user-text` and `user-message`
|
|
204
|
+
values. Active steering and host resume input emit `runtime-input` events. A
|
|
205
|
+
`runtime-input` is runtime/API-originated input mapped internally to the model's
|
|
206
|
+
user role. It is distinct from human-origin `user-text` and `user-message`
|
|
207
|
+
events.
|
|
110
208
|
|
|
111
209
|
Runtime input windows are tied to synchronized events:
|
|
112
210
|
|
|
@@ -138,204 +236,105 @@ for await (const event of run.events()) {
|
|
|
138
236
|
pending steering path or, when idle, when a new run is scheduled. It does not wait
|
|
139
237
|
for a later model snapshot.
|
|
140
238
|
|
|
141
|
-
##
|
|
142
|
-
|
|
143
|
-
Use `session.overlay(input)` for turn-scoped model context that should affect
|
|
144
|
-
the next model snapshot without becoming canonical session history. It accepts
|
|
145
|
-
the same input shapes as `send()` and `steer()` and has no options in v0.
|
|
146
|
-
|
|
147
|
-
Overlay context accepted before the first model inference in a turn is composed
|
|
148
|
-
above the current user prompt. This keeps the current prompt near the bottom of
|
|
149
|
-
the model snapshot while still giving the model fresh runtime context first.
|
|
150
|
-
Overlay context accepted after a model inference has already happened is
|
|
151
|
-
append-only: it is added after the messages that already existed for that turn
|
|
152
|
-
and never moves earlier content around.
|
|
153
|
-
|
|
154
|
-
```ts
|
|
155
|
-
const session = agent.session("room:123:user:456");
|
|
156
|
-
|
|
157
|
-
await session.overlay("Current wall-clock time: 2026-06-05T19:00:00Z");
|
|
158
|
-
const run = await session.send("What should I do next?");
|
|
159
|
-
|
|
160
|
-
for await (const event of run.events()) {
|
|
161
|
-
if (event.type === "step-end" && needsOneMorePass()) {
|
|
162
|
-
await session.overlay("Additional constraint: answer in two sentences.");
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Active `step-end` overlays intentionally continue the current turn for one more
|
|
168
|
-
model snapshot, like `step-end` steering, but they are not persisted as
|
|
169
|
-
`runtime-input`. Idle overlays do not start a turn; they queue for the next
|
|
170
|
-
`send()` on that session.
|
|
171
|
-
|
|
172
|
-
Runs emit `overlay-accepted` when overlay input is accepted and
|
|
173
|
-
`overlay-expired` when the turn-scoped frame is discarded at turn end, abort,
|
|
174
|
-
error, or kill. Overlay text is not written to stored history, not encoded in
|
|
175
|
-
session snapshots, and does not survive session reload.
|
|
176
|
-
|
|
177
|
-
## Plugins, Session Storage, Memory, And Compaction
|
|
239
|
+
## Session storage and portability
|
|
178
240
|
|
|
179
241
|
The runtime owns full session state encoding and history compaction semantics.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
```ts
|
|
183
|
-
import { Agent } from "@minpeter/pss-runtime";
|
|
184
|
-
import { compaction, memory, sessions } from "@minpeter/pss-runtime/plugins";
|
|
185
|
-
|
|
186
|
-
const agent = await Agent.create({
|
|
187
|
-
model,
|
|
188
|
-
plugins: [sessions.file(".pss/sessions"), memory(), compaction()],
|
|
189
|
-
});
|
|
190
|
-
```
|
|
242
|
+
Adapters own persistence only through `SessionStore`:
|
|
191
243
|
|
|
192
|
-
|
|
244
|
+
Stored session state is an opaque, versioned runtime snapshot for continuation.
|
|
245
|
+
Do not inspect it as a replay log; exact replay should be modeled separately as
|
|
246
|
+
an `AgentEvent` log if that capability is added later.
|
|
193
247
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
non-persistent context before the turn ends. Plugin `overlay` is available from
|
|
198
|
-
`turn.before`, `step.before`, and `step.after`; `turn.after` runs after the
|
|
199
|
-
turn-scoped overlay frame is closed and rejects overlay calls. App-level control
|
|
200
|
-
should stay with `run.events()` plus `session.steer()` or `session.overlay()`;
|
|
201
|
-
plugin lifecycle is for reusable policy.
|
|
248
|
+
`SessionStore` is snapshot-only. It does not own background task ids, run
|
|
249
|
+
leases, checkpoints, notification inbox state, or scheduling. Those live on the
|
|
250
|
+
optional `host` execution contract.
|
|
202
251
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
252
|
+
Custom stores own version generation. `load(key)` returns the opaque `state` with
|
|
253
|
+
the store-minted `version`; `commit(key, { state }, { expectedVersion })` receives
|
|
254
|
+
state only and should reject stale versions by returning `{ ok: false, reason:
|
|
255
|
+
"conflict" }`. On success, the store persists `{ state, version }` and returns the
|
|
256
|
+
new version to the runtime. `delete(key)` removes the persisted session for that
|
|
257
|
+
key.
|
|
207
258
|
|
|
208
259
|
```ts
|
|
209
|
-
import {
|
|
210
|
-
|
|
211
|
-
const continuePlugin = definePlugin({
|
|
212
|
-
name: "continue-policy",
|
|
213
|
-
setup(host) {
|
|
214
|
-
host.on("step.after", async ({ result, history, overlay, steer, stepIndex }) => {
|
|
215
|
-
if (result === "completed" && stepIndex === 0 && shouldContinueWork(history)) {
|
|
216
|
-
await overlay("Continue, but do not persist this policy text.");
|
|
217
|
-
await steer("Continue. The task is not complete yet.");
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
},
|
|
221
|
-
});
|
|
260
|
+
import { MemorySessionStore } from "@minpeter/pss-runtime/session-store/memory";
|
|
222
261
|
|
|
223
|
-
const agent =
|
|
262
|
+
const agent = new Agent({
|
|
263
|
+
host: {
|
|
264
|
+
sessionStore: new MemorySessionStore(), // default when omitted
|
|
265
|
+
},
|
|
224
266
|
model,
|
|
225
|
-
|
|
267
|
+
namespace: "support-agent",
|
|
226
268
|
});
|
|
227
269
|
```
|
|
228
270
|
|
|
229
|
-
|
|
230
|
-
|
|
271
|
+
For durable sessions, use the exported file POC. Set a stable `namespace` when
|
|
272
|
+
subagents also use durable stores, so reconstructed agents map the same parent
|
|
273
|
+
session and child `sessionKey` suffixes back to the same child transcripts:
|
|
231
274
|
|
|
232
275
|
```ts
|
|
233
|
-
|
|
234
|
-
name: "turn-audit",
|
|
235
|
-
setup(host) {
|
|
236
|
-
host.on("turn.after", ({ result, sessionKey }) => {
|
|
237
|
-
recordTurnResult(sessionKey, result);
|
|
238
|
-
});
|
|
239
|
-
},
|
|
240
|
-
});
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
Tool policy hooks apply to runtime-owned tools in the `Agent.create({ model,
|
|
244
|
-
tools })` path, including tools registered by plugins. Custom `llm` callers own
|
|
245
|
-
their tool execution and do not receive synthetic tool hook events.
|
|
276
|
+
import { FileSessionStore } from "@minpeter/pss-runtime/session-store/file";
|
|
246
277
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
`reject-and-continue` skips the original tool and returns a rejection payload to
|
|
251
|
-
the model, `synthesize` skips the original tool and returns a synthetic output,
|
|
252
|
-
and `error` fails the active run.
|
|
253
|
-
|
|
254
|
-
```ts
|
|
255
|
-
import { definePlugin } from "@minpeter/pss-runtime";
|
|
256
|
-
|
|
257
|
-
const toolPolicyPlugin = definePlugin({
|
|
258
|
-
name: "tool-policy",
|
|
259
|
-
setup(host) {
|
|
260
|
-
host.on("tool.call", ({ input, tool }) => {
|
|
261
|
-
if (tool === "delete_file") {
|
|
262
|
-
return {
|
|
263
|
-
action: "reject-and-continue",
|
|
264
|
-
message: "delete_file is disabled in this workspace.",
|
|
265
|
-
};
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (tool === "search" && shouldNarrowSearch(input)) {
|
|
269
|
-
return { action: "modify", input: narrowSearchInput(input) };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return { action: "allow" };
|
|
273
|
-
});
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
`tool.result` runs after allowed/modified execution, rejected calls, synthesized
|
|
279
|
-
calls, and original tool errors. It can observe or replace the model-facing
|
|
280
|
-
result with `{ status: "done", output }`, `{ status: "error", error, output }`,
|
|
281
|
-
or `{ status: "cancelled", error, output }`. Replacements flow into later
|
|
282
|
-
`tool.result` handlers.
|
|
283
|
-
|
|
284
|
-
```ts
|
|
285
|
-
const resultPolicyPlugin = definePlugin({
|
|
286
|
-
name: "tool-result-policy",
|
|
287
|
-
setup(host) {
|
|
288
|
-
host.on("tool.result", ({ output, status, tool }) => {
|
|
289
|
-
if (tool === "read_secret" && status === "done") {
|
|
290
|
-
return {
|
|
291
|
-
status: "done",
|
|
292
|
-
output: redactSecretOutput(output),
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
});
|
|
278
|
+
const agent = new Agent({
|
|
279
|
+
host: {
|
|
280
|
+
sessionStore: new FileSessionStore(".pss/sessions"),
|
|
296
281
|
},
|
|
282
|
+
model,
|
|
283
|
+
namespace: "support-agent",
|
|
297
284
|
});
|
|
298
285
|
```
|
|
299
286
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
store
|
|
287
|
+
Hosts that need durable runs pass `host:` into `Agent`. A durable host owns
|
|
288
|
+
execution state, scheduling, and its session snapshot store through
|
|
289
|
+
`store.sessions`; `ExecutionHost` does not accept a separate `sessionStore`
|
|
290
|
+
override.
|
|
303
291
|
|
|
304
292
|
```ts
|
|
305
|
-
import
|
|
306
|
-
import {
|
|
293
|
+
import { Agent } from "@minpeter/pss-runtime";
|
|
294
|
+
import {
|
|
295
|
+
createInMemoryExecutionHost,
|
|
296
|
+
type ExecutionHost,
|
|
297
|
+
} from "@minpeter/pss-runtime/execution";
|
|
307
298
|
|
|
308
|
-
|
|
299
|
+
const host = createInMemoryExecutionHost();
|
|
309
300
|
|
|
310
|
-
const agent =
|
|
301
|
+
const agent = new Agent({
|
|
302
|
+
host,
|
|
311
303
|
model,
|
|
312
|
-
|
|
304
|
+
namespace: "support-agent",
|
|
313
305
|
});
|
|
314
|
-
```
|
|
315
306
|
|
|
316
|
-
|
|
307
|
+
const durableHost: ExecutionHost = {
|
|
308
|
+
capabilities: { backgroundSubagents: "durable" },
|
|
309
|
+
scheduler,
|
|
310
|
+
store,
|
|
311
|
+
};
|
|
312
|
+
```
|
|
317
313
|
|
|
318
|
-
|
|
319
|
-
an `AgentEvent` log if that capability is added later.
|
|
314
|
+
## Supported Deployment Shapes
|
|
320
315
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
the runtime.
|
|
316
|
+
The runtime supports both long-running Node.js processes and edge hosts that
|
|
317
|
+
reconstruct runtime objects between turns. The same public DX stays centered on
|
|
318
|
+
`new Agent({ subagents: [...] })`; host-specific durability and scheduling live
|
|
319
|
+
behind the `host` boundary.
|
|
326
320
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
321
|
+
Long-running Node.js can keep an `Agent` and `SessionHandle` alive across turns.
|
|
322
|
+
The default in-memory host advertises in-process background subagent capability,
|
|
323
|
+
so `run_in_background`, `background_output`, and `background_cancel` remain
|
|
324
|
+
available while that process owns the work. `FileSessionStore` persists session
|
|
325
|
+
snapshots only; it does not make background work durable by itself.
|
|
331
326
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
327
|
+
Cloudflare Durable Objects and similar edge hosts should reconstruct `Agent`
|
|
328
|
+
objects per turn and persist opaque session state through `store.sessions`.
|
|
329
|
+
When a host does not advertise background subagent capability, the runtime hides
|
|
330
|
+
background tools from the parent model and exposes blocking delegation only.
|
|
331
|
+
This avoids pretending that in-memory background work survived a hibernation,
|
|
332
|
+
isolate restart, or request deadline.
|
|
335
333
|
|
|
336
|
-
|
|
334
|
+
See `examples/cloudflare-edge-subagent` for an edge-hosted turn loop with a
|
|
335
|
+
Worker/Durable Object-shaped host, and `examples/subagent` for a long-running
|
|
336
|
+
local background subagent flow.
|
|
337
337
|
|
|
338
|
-
Cloudflare Durable Objects are a future adapter target, not a runtime dependency.
|
|
339
338
|
The same core API supports room/user/session routing through stable session keys.
|
|
340
339
|
|
|
341
340
|
Recommended key patterns:
|
|
@@ -344,6 +343,39 @@ Recommended key patterns:
|
|
|
344
343
|
- Per-user memory inside room: `room:<roomId>:user:<userId>`
|
|
345
344
|
- Ticketed workspace flows: `tenant:<tenantId>:ticket:<ticketId>`
|
|
346
345
|
|
|
347
|
-
In a Durable Object, map the
|
|
348
|
-
durable across hibernation/restores, while in-memory state remains
|
|
349
|
-
Do not store canonical agent session state in memory
|
|
346
|
+
In a Durable Object, map the execution store contract to `ctx.storage` so DO
|
|
347
|
+
storage is durable across hibernation/restores, while in-memory state remains
|
|
348
|
+
request-local. Do not store canonical agent session or run state in memory
|
|
349
|
+
attachments.
|
|
350
|
+
|
|
351
|
+
Durable background subagents require a host capability that owns task ids,
|
|
352
|
+
attempts, leases, checkpoints, cancellation, scheduling, and completion
|
|
353
|
+
notifications. The Cloudflare edge example includes a Worker/Durable
|
|
354
|
+
Object-shaped host that persists scheduled runs and session prompts, sets alarms, and
|
|
355
|
+
resumes work through `Agent.resume(...)`.
|
|
356
|
+
|
|
357
|
+
## Checkpoints and Cancellation
|
|
358
|
+
|
|
359
|
+
Resume is safe only at committed boundaries. Durable hosts can checkpoint before
|
|
360
|
+
and after model steps, around notifications, before child run creation, when a
|
|
361
|
+
child link is committed, and when a run suspends. If a process is killed inside a
|
|
362
|
+
provider call or unsafe tool execution, resume rolls back to the last committed
|
|
363
|
+
checkpoint and may re-enter the operation.
|
|
364
|
+
|
|
365
|
+
When `Agent` receives an `ExecutionHost`, high-level model turns create a
|
|
366
|
+
`user-turn` run record and thread tool execution context into managed model
|
|
367
|
+
calls. Tools are checkpointed before and after execution and receive stable
|
|
368
|
+
`attempt`, `idempotencyKey`, `retryPolicy`, `signal`, and public `toolCallId`
|
|
369
|
+
values. The `@minpeter/pss-runtime/execution` entrypoint also exposes the same
|
|
370
|
+
low-level tool execution checkpoint types for custom resume runners built
|
|
371
|
+
directly on `createLlm`.
|
|
372
|
+
|
|
373
|
+
These checkpoints are rollback boundaries, not a complete host adapter by
|
|
374
|
+
themselves. Edge hosts still need durable scheduling, leases, resume workers,
|
|
375
|
+
and notification resume handling; externally visible side-effect tools still need
|
|
376
|
+
idempotent execution or a manual recovery flow.
|
|
377
|
+
|
|
378
|
+
Cancellation is persisted before aborting active work. Parent `delete()` and
|
|
379
|
+
`kill()` mark linked child runs as `cancelled` through the execution store before
|
|
380
|
+
falling back to process-local cleanup, so stale child completions cannot enqueue
|
|
381
|
+
new parent notifications.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { executionHost } from "./execution/host.js";
|
|
2
|
+
import { cancelBackgroundChildRun } from "./subagent-background-child-run.js";
|
|
3
|
+
//#region src/agent-child-runs.ts
|
|
4
|
+
async function cancelDurableChildRuns(host, parentRunId) {
|
|
5
|
+
const durableHost = executionHost(host);
|
|
6
|
+
if (!durableHost) return;
|
|
7
|
+
const childRuns = await durableHost.store.runs.listByParentRunId(parentRunId);
|
|
8
|
+
await Promise.all(childRuns.map((run) => cancelBackgroundChildRun({
|
|
9
|
+
executionHost: durableHost,
|
|
10
|
+
runId: run.runId
|
|
11
|
+
})));
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { cancelDurableChildRuns };
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=agent-child-runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-child-runs.js","names":[],"sources":["../src/agent-child-runs.ts"],"sourcesContent":["import { executionHost } from \"./execution/host\";\nimport type { AgentHost } from \"./execution/types\";\nimport { cancelBackgroundChildRun } from \"./subagent-background-child-run\";\n\nexport async function cancelDurableChildRuns(\n host: AgentHost,\n parentRunId: string\n): Promise<void> {\n const durableHost = executionHost(host);\n if (!durableHost) {\n return;\n }\n\n const childRuns = await durableHost.store.runs.listByParentRunId(parentRunId);\n await Promise.all(\n childRuns.map((run) =>\n cancelBackgroundChildRun({\n executionHost: durableHost,\n runId: run.runId,\n })\n )\n );\n}\n"],"mappings":";;;AAIA,eAAsB,uBACpB,MACA,aACe;CACf,MAAM,cAAc,cAAc,IAAI;CACtC,IAAI,CAAC,aACH;CAGF,MAAM,YAAY,MAAM,YAAY,MAAM,KAAK,kBAAkB,WAAW;CAC5E,MAAM,QAAQ,IACZ,UAAU,KAAK,QACb,yBAAyB;EACvB,eAAe;EACf,OAAO,IAAI;CACb,CAAC,CACH,CACF;AACF"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/agent-host-capabilities.ts
|
|
2
|
+
function supportsBackgroundSubagents(host, executionHost) {
|
|
3
|
+
const capability = host.capabilities?.backgroundSubagents;
|
|
4
|
+
return capability === "in-process" || capability === "durable" && executionHost !== void 0;
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { supportsBackgroundSubagents };
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=agent-host-capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-host-capabilities.js","names":[],"sources":["../src/agent-host-capabilities.ts"],"sourcesContent":["import type { AgentHost, ExecutionHost } from \"./execution/types\";\n\nexport function supportsBackgroundSubagents(\n host: AgentHost,\n executionHost: ExecutionHost | undefined\n): boolean {\n const capability = host.capabilities?.backgroundSubagents;\n return (\n capability === \"in-process\" ||\n (capability === \"durable\" && executionHost !== undefined)\n );\n}\n"],"mappings":";AAEA,SAAgB,4BACd,MACA,eACS;CACT,MAAM,aAAa,KAAK,cAAc;CACtC,OACE,eAAe,gBACd,eAAe,aAAa,kBAAkB,KAAA;AAEnD"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { executionHost } from "./execution/host.js";
|
|
2
|
+
import { MemorySessionStore } from "./session/store/memory.js";
|
|
3
|
+
//#region src/agent-host-session-store.ts
|
|
4
|
+
function sessionStoreForHost(host) {
|
|
5
|
+
if ("sessionStore" in host && host.sessionStore) return host.sessionStore;
|
|
6
|
+
const hostExecution = executionHost(host);
|
|
7
|
+
return hostExecution ? hostExecution.store.sessions : new MemorySessionStore();
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { sessionStoreForHost };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=agent-host-session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-host-session-store.js","names":[],"sources":["../src/agent-host-session-store.ts"],"sourcesContent":["import { executionHost } from \"./execution/host\";\nimport type { AgentHost } from \"./execution/types\";\nimport { MemorySessionStore } from \"./session/store/memory\";\nimport type { SessionStore } from \"./session/store/types\";\n\nexport function sessionStoreForHost(host: AgentHost): SessionStore {\n if (\"sessionStore\" in host && host.sessionStore) {\n return host.sessionStore;\n }\n\n const hostExecution = executionHost(host);\n return hostExecution\n ? hostExecution.store.sessions\n : new MemorySessionStore();\n}\n"],"mappings":";;;AAKA,SAAgB,oBAAoB,MAA+B;CACjE,IAAI,kBAAkB,QAAQ,KAAK,cACjC,OAAO,KAAK;CAGd,MAAM,gBAAgB,cAAc,IAAI;CACxC,OAAO,gBACH,cAAc,MAAM,WACpB,IAAI,mBAAmB;AAC7B"}
|