agentfootprint 7.18.0 → 7.19.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/dist/adapters/hosting/agentcore.js +23 -9
- package/dist/adapters/hosting/agentcore.js.map +1 -1
- package/dist/core/Agent.js +6 -0
- package/dist/core/Agent.js.map +1 -1
- package/dist/core/agent/stages/toolCalls.js +13 -0
- package/dist/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/core/durabilityBarrier.js +68 -0
- package/dist/core/durabilityBarrier.js.map +1 -0
- package/dist/esm/adapters/hosting/agentcore.d.ts +4 -4
- package/dist/esm/adapters/hosting/agentcore.js +24 -10
- package/dist/esm/adapters/hosting/agentcore.js.map +1 -1
- package/dist/esm/core/Agent.js +6 -0
- package/dist/esm/core/Agent.js.map +1 -1
- package/dist/esm/core/agent/stages/toolCalls.d.ts +21 -0
- package/dist/esm/core/agent/stages/toolCalls.js +13 -0
- package/dist/esm/core/agent/stages/toolCalls.js.map +1 -1
- package/dist/esm/core/durabilityBarrier.d.ts +61 -0
- package/dist/esm/core/durabilityBarrier.js +63 -0
- package/dist/esm/core/durabilityBarrier.js.map +1 -0
- package/dist/esm/hosting/durability.d.ts +92 -0
- package/dist/esm/hosting/durability.js +174 -0
- package/dist/esm/hosting/durability.js.map +1 -0
- package/dist/esm/hosting/envelope.d.ts +75 -14
- package/dist/esm/hosting/envelope.js +141 -16
- package/dist/esm/hosting/envelope.js.map +1 -1
- package/dist/esm/hosting/errors.d.ts +60 -11
- package/dist/esm/hosting/errors.js +92 -18
- package/dist/esm/hosting/errors.js.map +1 -1
- package/dist/esm/hosting/httpHost.d.ts +17 -1
- package/dist/esm/hosting/httpHost.js +42 -6
- package/dist/esm/hosting/httpHost.js.map +1 -1
- package/dist/esm/hosting/index.d.ts +7 -4
- package/dist/esm/hosting/index.js +6 -3
- package/dist/esm/hosting/index.js.map +1 -1
- package/dist/esm/hosting/nodeHost.d.ts +4 -2
- package/dist/esm/hosting/nodeHost.js +14 -3
- package/dist/esm/hosting/nodeHost.js.map +1 -1
- package/dist/esm/hosting/standingAgent.d.ts +22 -7
- package/dist/esm/hosting/standingAgent.js +144 -32
- package/dist/esm/hosting/standingAgent.js.map +1 -1
- package/dist/esm/hosting/types.d.ts +193 -19
- package/dist/hosting/durability.js +178 -0
- package/dist/hosting/durability.js.map +1 -0
- package/dist/hosting/envelope.js +146 -18
- package/dist/hosting/envelope.js.map +1 -1
- package/dist/hosting/errors.js +95 -19
- package/dist/hosting/errors.js.map +1 -1
- package/dist/hosting/httpHost.js +42 -6
- package/dist/hosting/httpHost.js.map +1 -1
- package/dist/hosting/index.js +10 -2
- package/dist/hosting/index.js.map +1 -1
- package/dist/hosting/nodeHost.js +14 -3
- package/dist/hosting/nodeHost.js.map +1 -1
- package/dist/hosting/standingAgent.js +142 -30
- package/dist/hosting/standingAgent.js.map +1 -1
- package/dist/types/adapters/hosting/agentcore.d.ts +4 -4
- package/dist/types/adapters/hosting/agentcore.d.ts.map +1 -1
- package/dist/types/core/Agent.d.ts.map +1 -1
- package/dist/types/core/agent/stages/toolCalls.d.ts +21 -0
- package/dist/types/core/agent/stages/toolCalls.d.ts.map +1 -1
- package/dist/types/core/durabilityBarrier.d.ts +62 -0
- package/dist/types/core/durabilityBarrier.d.ts.map +1 -0
- package/dist/types/hosting/durability.d.ts +93 -0
- package/dist/types/hosting/durability.d.ts.map +1 -0
- package/dist/types/hosting/envelope.d.ts +75 -14
- package/dist/types/hosting/envelope.d.ts.map +1 -1
- package/dist/types/hosting/errors.d.ts +60 -11
- package/dist/types/hosting/errors.d.ts.map +1 -1
- package/dist/types/hosting/httpHost.d.ts +17 -1
- package/dist/types/hosting/httpHost.d.ts.map +1 -1
- package/dist/types/hosting/index.d.ts +7 -4
- package/dist/types/hosting/index.d.ts.map +1 -1
- package/dist/types/hosting/nodeHost.d.ts +4 -2
- package/dist/types/hosting/nodeHost.d.ts.map +1 -1
- package/dist/types/hosting/standingAgent.d.ts +22 -7
- package/dist/types/hosting/standingAgent.d.ts.map +1 -1
- package/dist/types/hosting/types.d.ts +193 -19
- package/dist/types/hosting/types.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -5,17 +5,26 @@
|
|
|
5
5
|
* agent,
|
|
6
6
|
* sessions: memorySessions(),
|
|
7
7
|
* host: nodeHost({ port: 8080 }),
|
|
8
|
+
* durability: 'sync', // optional; 'exit' is the default
|
|
8
9
|
* });
|
|
9
10
|
*
|
|
10
11
|
* One request at a time it does four things: wake and hydrate the session,
|
|
11
|
-
*
|
|
12
|
+
* continue that session or start a fresh one, persist what the run leaves
|
|
12
13
|
* behind, then reply. Everything else is somebody else's job — the host carries
|
|
13
14
|
* bytes, the store keeps them, the agent thinks.
|
|
14
15
|
*
|
|
15
|
-
* ──
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* ── A run has three ends, and this composer honours all three ────────────────
|
|
17
|
+
* It answered, it asked a person something, or it failed. An answer completes
|
|
18
|
+
* the reply and stores a conversation. A QUESTION stores the paused run as
|
|
19
|
+
* `'flowchart-v1'` and leaves through `reply.awaiting(...)` — its own terminal,
|
|
20
|
+
* never `fail`, because a pause is unfinished work and reporting it as a failure
|
|
21
|
+
* tells every dashboard downstream something untrue. A later request for that
|
|
22
|
+
* session carrying `decision` continues the run from exactly where it stopped.
|
|
23
|
+
*
|
|
24
|
+
* ── Resuming a CONVERSATION is a REPLAY, and that has a cost ─────────────────
|
|
25
|
+
* A stored conversation is restored through `agent.resumeOnError(...)`, and this
|
|
26
|
+
* is its caveat, stated here in the words the Agent states it in, because a
|
|
27
|
+
* composition that hides the caveat of the thing it composes is worse than no
|
|
19
28
|
* composition at all:
|
|
20
29
|
*
|
|
21
30
|
* > **Tool re-execution / idempotency**: tool side effects from the FAILED
|
|
@@ -25,6 +34,10 @@
|
|
|
25
34
|
* > emails, DB writes) must be idempotent — key on stable call content, not
|
|
26
35
|
* > `ctx.toolCallId` (a re-issued call gets a new id).
|
|
27
36
|
*
|
|
37
|
+
* `durability` is the dial that bounds how much of that a crash can cost you.
|
|
38
|
+
* Resuming a PAUSED run is different in kind: it is not a replay at all — the
|
|
39
|
+
* engine continues from its own checkpoint, and no earlier tool call re-runs.
|
|
40
|
+
*
|
|
28
41
|
* ── Why one run at a time ───────────────────────────────────────────────────
|
|
29
42
|
* An Agent instance holds per-run state on itself, and this composer shares ONE
|
|
30
43
|
* instance across every session. Two runs overlapping on it do not crash —
|
|
@@ -43,14 +56,16 @@
|
|
|
43
56
|
* mechanism of its own.
|
|
44
57
|
*/
|
|
45
58
|
import { isPaused } from '../core/pause.js';
|
|
46
|
-
import {
|
|
47
|
-
import {
|
|
59
|
+
import { durableWriter } from './durability.js';
|
|
60
|
+
import { readEnvelope, readPausedRun, toEnvelope, toPausedEnvelope } from './envelope.js';
|
|
61
|
+
import { AwaitingDecisionError, ConcurrentRunError, NoPendingAskError, PauseNotCarriedError, } from './errors.js';
|
|
48
62
|
/**
|
|
49
63
|
* Serve one agent, with per-session conversation memory, on any
|
|
50
64
|
* {@link AgentHost}.
|
|
51
65
|
*
|
|
52
|
-
* Resolves once the host is live. Closing the returned handle closes the host
|
|
53
|
-
*
|
|
66
|
+
* Resolves once the host is live. Closing the returned handle closes the host,
|
|
67
|
+
* detaches the listeners this composer added to the agent, and removes its
|
|
68
|
+
* durability wiring.
|
|
54
69
|
*
|
|
55
70
|
* @example
|
|
56
71
|
* const handle = await standingAgent({
|
|
@@ -58,17 +73,21 @@ import { ConcurrentRunError, PauseNotCarriedError } from './errors.js';
|
|
|
58
73
|
* sessions: memorySessions(),
|
|
59
74
|
* host: nodeHost({ port: 0 }),
|
|
60
75
|
* onConcurrentInvoke: 'enqueue',
|
|
76
|
+
* durability: 'sync',
|
|
61
77
|
* });
|
|
62
78
|
* process.on('SIGTERM', () => void handle.close());
|
|
63
79
|
*/
|
|
64
80
|
export async function standingAgent(options) {
|
|
65
81
|
const { agent, sessions, host } = options;
|
|
66
82
|
const policy = options.onConcurrentInvoke ?? 'reject';
|
|
83
|
+
const durability = options.durability ?? 'exit';
|
|
67
84
|
// Runs are serialized, so at any moment there is at most one active reply and
|
|
68
85
|
// at most one active run to name in a refusal.
|
|
69
86
|
let activeReply;
|
|
70
87
|
let activeRunId;
|
|
71
88
|
let activeSession;
|
|
89
|
+
/** The REAL session id of the active run — never the anonymous placeholder. */
|
|
90
|
+
let activeSessionId;
|
|
72
91
|
const queued = [];
|
|
73
92
|
let chain = Promise.resolve();
|
|
74
93
|
let anonymous = 0;
|
|
@@ -82,6 +101,19 @@ export async function standingAgent(options) {
|
|
|
82
101
|
if (typeof content === 'string' && content.length > 0)
|
|
83
102
|
activeReply?.emit?.(content);
|
|
84
103
|
});
|
|
104
|
+
// Under 'exit' NOTHING is built: no observer on the agent, no barrier, no
|
|
105
|
+
// per-commit work. That is what "the default is byte-identical" means here —
|
|
106
|
+
// not a mode that happens to write once, but wiring that is never installed.
|
|
107
|
+
const writer = durability === 'exit'
|
|
108
|
+
? undefined
|
|
109
|
+
: durableWriter({
|
|
110
|
+
mode: durability,
|
|
111
|
+
session: () => activeSessionId,
|
|
112
|
+
runId: () => activeRunId,
|
|
113
|
+
write: (sessionId, conversation) => sessions.persist(sessionId, toEnvelope(conversation)),
|
|
114
|
+
});
|
|
115
|
+
const detachWriter = writer ? agent.attach(writer.recorder) : undefined;
|
|
116
|
+
const uninstallBarrier = writer?.install(agent);
|
|
85
117
|
/** Queue `work` behind everything already waiting, FIFO. */
|
|
86
118
|
function serialize(sessionKey, work) {
|
|
87
119
|
if (policy === 'reject' && (activeSession === sessionKey || queued.includes(sessionKey))) {
|
|
@@ -119,40 +151,101 @@ export async function standingAgent(options) {
|
|
|
119
151
|
};
|
|
120
152
|
async function answerOne(runner, store, request, reply, sessionId) {
|
|
121
153
|
activeReply = reply;
|
|
154
|
+
// The writer only ever writes for the run it is inside; an anonymous
|
|
155
|
+
// request has nowhere to write to and gets nothing.
|
|
156
|
+
activeSessionId = sessionId;
|
|
122
157
|
try {
|
|
123
158
|
let prior;
|
|
159
|
+
let paused;
|
|
124
160
|
if (sessionId !== undefined) {
|
|
125
|
-
|
|
161
|
+
// A request carrying a decision is waking this session to CONTINUE a
|
|
162
|
+
// run, which is a different thing to ask a store to be ready for.
|
|
163
|
+
await store.onWake?.(sessionId, request.decision !== undefined ? 'resume' : 'invoke');
|
|
126
164
|
const stored = await store.hydrate(sessionId);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
165
|
+
if (stored !== undefined) {
|
|
166
|
+
// Both readers throw by name on a format this runtime cannot read —
|
|
167
|
+
// better a loud refusal than an agent answering from half a session.
|
|
168
|
+
if (stored.format === 'flowchart-v1')
|
|
169
|
+
paused = readPausedRun(stored);
|
|
170
|
+
else
|
|
171
|
+
prior = readEnvelope(stored);
|
|
172
|
+
}
|
|
131
173
|
}
|
|
132
174
|
// The signal reaches tool execution, tool discovery and skill-entry
|
|
133
175
|
// scoring. It does NOT currently reach the LLM call, so a caller who
|
|
134
176
|
// hangs up mid-generation stops the tools, not the token stream.
|
|
135
177
|
const runOptions = request.signal ? { env: { signal: request.signal } } : undefined;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
178
|
+
// ── The one discriminant ─────────────────────────────────────────
|
|
179
|
+
// A request carrying `decision` answers a pending question; a request
|
|
180
|
+
// without one is a new message. Never inferred from the text: reading
|
|
181
|
+
// approval out of prose is a guess, and a consent gate may not be built
|
|
182
|
+
// on a guess.
|
|
183
|
+
if (request.decision !== undefined) {
|
|
184
|
+
if (!paused) {
|
|
185
|
+
reply.fail(new NoPendingAskError(sessionId ?? '(anonymous)'));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
await deliver(await runner.resume(paused.checkpoint, request.decision, runOptions), runner, store, reply, sessionId);
|
|
141
189
|
return;
|
|
142
190
|
}
|
|
143
|
-
if (sessionId !== undefined) {
|
|
144
|
-
|
|
145
|
-
//
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (conversation)
|
|
149
|
-
await store.persist(sessionId, toEnvelope(conversation));
|
|
191
|
+
if (paused && sessionId !== undefined) {
|
|
192
|
+
// The message is not run and the pause is not discarded. Both of the
|
|
193
|
+
// other options lose something a person cared about.
|
|
194
|
+
reply.fail(new AwaitingDecisionError(sessionId, paused.pending));
|
|
195
|
+
return;
|
|
150
196
|
}
|
|
151
|
-
|
|
197
|
+
const output = prior
|
|
198
|
+
? await runner.resumeOnError(continueConversation(prior, request.input), runOptions)
|
|
199
|
+
: await runner.run({ message: request.input }, runOptions);
|
|
200
|
+
await deliver(output, runner, store, reply, sessionId);
|
|
201
|
+
}
|
|
202
|
+
catch (err) {
|
|
203
|
+
// A run that threw still has to drain. A write left in flight would race
|
|
204
|
+
// the NEXT turn's terminal write and could land after it. The drain's own
|
|
205
|
+
// failure is swallowed here and only here: the caller is already being
|
|
206
|
+
// told about the run's error, and replacing it with a storage error would
|
|
207
|
+
// hide the thing that actually went wrong.
|
|
208
|
+
await writer?.settle().catch(() => undefined);
|
|
209
|
+
throw err;
|
|
152
210
|
}
|
|
153
211
|
finally {
|
|
154
212
|
activeReply = undefined;
|
|
213
|
+
activeSessionId = undefined;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/** Store what the run left behind, then end the reply with the right terminal. */
|
|
217
|
+
async function deliver(output, runner, store, reply, sessionId) {
|
|
218
|
+
// Mid-run writes settle FIRST. Ordering, not tidiness: an 'async' write
|
|
219
|
+
// still in flight would otherwise land after the terminal envelope and
|
|
220
|
+
// overwrite it — a stored pause quietly demoted back to a conversation,
|
|
221
|
+
// and the question a person was asked gone with it. It also rethrows a
|
|
222
|
+
// store that refused this run's progress, so a broken store fails the
|
|
223
|
+
// request rather than being reported as a clean answer.
|
|
224
|
+
await writer?.settle();
|
|
225
|
+
if (isPaused(output)) {
|
|
226
|
+
const pending = describePause(output, sessionId);
|
|
227
|
+
const conversation = sessionId === undefined ? undefined : runner.checkpoint();
|
|
228
|
+
if (sessionId !== undefined && conversation) {
|
|
229
|
+
await store.persist(sessionId, toPausedEnvelope({ checkpoint: output.checkpoint, conversation, pending }));
|
|
230
|
+
if (reply.awaiting) {
|
|
231
|
+
reply.awaiting(pending);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Either there was nowhere to store it, or the host cannot describe a
|
|
236
|
+
// question. Both are refusals about THIS reply, not about the run.
|
|
237
|
+
reply.fail(new PauseNotCarriedError(pending.tool, sessionId, conversation !== undefined));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (sessionId !== undefined) {
|
|
241
|
+
const conversation = runner.checkpoint();
|
|
242
|
+
// Persist BEFORE answering: the caller learns the answer only once the
|
|
243
|
+
// conversation that produced it is durable, so a queued next turn can
|
|
244
|
+
// never read state older than the answer already given.
|
|
245
|
+
if (conversation)
|
|
246
|
+
await store.persist(sessionId, toEnvelope(conversation));
|
|
155
247
|
}
|
|
248
|
+
reply.complete(typeof output === 'string' ? output : String(output));
|
|
156
249
|
}
|
|
157
250
|
const handle = await host.serve(handler);
|
|
158
251
|
// Keep whatever the adapter put on its own handle (nodeHost's bound `url`,
|
|
@@ -165,6 +258,8 @@ export async function standingAgent(options) {
|
|
|
165
258
|
await handle.close();
|
|
166
259
|
offTurnStart();
|
|
167
260
|
offToken();
|
|
261
|
+
uninstallBarrier?.();
|
|
262
|
+
detachWriter?.();
|
|
168
263
|
},
|
|
169
264
|
};
|
|
170
265
|
}
|
|
@@ -183,10 +278,27 @@ function continueConversation(prior, input) {
|
|
|
183
278
|
checkpointedAt: Date.now(),
|
|
184
279
|
};
|
|
185
280
|
}
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
281
|
+
/**
|
|
282
|
+
* Project a paused run into the part that is safe to hand back: the question,
|
|
283
|
+
* never the state.
|
|
284
|
+
*
|
|
285
|
+
* Every field is read from what the run actually recorded — the tool name and
|
|
286
|
+
* question the dispatch loop stamps on `pauseData`, the typed `checkIn`, the
|
|
287
|
+
* middleware `ask`. A field the run did not record is simply absent. Nothing
|
|
288
|
+
* here infers, summarises or invents, which is why the raw `pauseData` rides
|
|
289
|
+
* along untouched: the tool's author chose those words.
|
|
290
|
+
*/
|
|
291
|
+
function describePause(outcome, sessionId) {
|
|
292
|
+
const data = outcome.pauseData;
|
|
293
|
+
const tool = typeof data?.toolName === 'string' ? data.toolName : undefined;
|
|
294
|
+
const question = typeof data?.question === 'string' ? data.question : undefined;
|
|
295
|
+
return {
|
|
296
|
+
...(sessionId !== undefined && { sessionId }),
|
|
297
|
+
...(tool !== undefined && { tool }),
|
|
298
|
+
...(question !== undefined && { question }),
|
|
299
|
+
...(outcome.checkIn !== undefined && { checkIn: outcome.checkIn }),
|
|
300
|
+
...(outcome.ask !== undefined && { ask: outcome.ask }),
|
|
301
|
+
pauseData: outcome.pauseData,
|
|
302
|
+
};
|
|
191
303
|
}
|
|
192
304
|
//# sourceMappingURL=standingAgent.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"standingAgent.js","sourceRoot":"","sources":["../../../src/hosting/standingAgent.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"standingAgent.js","sourceRoot":"","sources":["../../../src/hosting/standingAgent.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
*
|
|
18
18
|
* Pattern: Ports & adapters (hexagonal). Role: the port side, exclusively.
|
|
19
19
|
*/
|
|
20
|
+
import type { FlowchartCheckpoint } from 'footprintjs';
|
|
20
21
|
import type { Agent } from '../core/Agent.js';
|
|
22
|
+
import type { CheckInRequest } from '../core/checkin.js';
|
|
23
|
+
import type { MiddlewareAsk } from '../core/pause.js';
|
|
21
24
|
import type { AgentRunCheckpoint } from '../core/runCheckpoint.js';
|
|
22
25
|
/**
|
|
23
26
|
* Something a host can do BEYOND the baseline of "accept a request, deliver one
|
|
@@ -37,6 +40,24 @@ export type HostCapability = 'streaming';
|
|
|
37
40
|
export interface HostRequest {
|
|
38
41
|
/** What the caller is asking. */
|
|
39
42
|
readonly input: string;
|
|
43
|
+
/**
|
|
44
|
+
* A person's answer to an outstanding {@link PendingAsk} — and **the one
|
|
45
|
+
* thing that distinguishes a resume from a new message.**
|
|
46
|
+
*
|
|
47
|
+
* Present ⇒ this request answers the run that paused on this session. Absent
|
|
48
|
+
* ⇒ this request is a new message. That is the whole contract, and it is a
|
|
49
|
+
* FIELD rather than an inference on purpose: reading approval out of prose
|
|
50
|
+
* ("yes, go ahead") is a guess, and a guess is not something a consent gate
|
|
51
|
+
* may be built on.
|
|
52
|
+
*
|
|
53
|
+
* The port never interprets it. It is handed to `agent.resume(checkpoint,
|
|
54
|
+
* decision)` exactly as it arrived — a {@link CheckInRequest} or a middleware
|
|
55
|
+
* `ask` is answered with the shipped `checkInApproved()` / `checkInDeclined()`
|
|
56
|
+
* vocabulary; a plain `askHuman` pause is answered with whatever that tool's
|
|
57
|
+
* author documented. Typed `unknown` because the library does not get to
|
|
58
|
+
* decide what a tool asked for.
|
|
59
|
+
*/
|
|
60
|
+
readonly decision?: unknown;
|
|
40
61
|
/**
|
|
41
62
|
* The conversation this request CLAIMS to belong to — caller data, exactly as
|
|
42
63
|
* the transport declared it (a JSON field, a header, a path segment).
|
|
@@ -57,13 +78,37 @@ export interface HostRequest {
|
|
|
57
78
|
readonly signal?: AbortSignal;
|
|
58
79
|
}
|
|
59
80
|
/**
|
|
60
|
-
* The one reply a request gets. Exactly one of {@link HostReply.complete}
|
|
61
|
-
* {@link HostReply.fail} ends it; a second call is
|
|
62
|
-
* to corrupt the wire.
|
|
81
|
+
* The one reply a request gets. Exactly one of {@link HostReply.complete},
|
|
82
|
+
* {@link HostReply.awaiting} or {@link HostReply.fail} ends it; a second call is
|
|
83
|
+
* ignored rather than allowed to corrupt the wire.
|
|
84
|
+
*
|
|
85
|
+
* Three terminals, because a run has three ends and only three: it answered, it
|
|
86
|
+
* stopped to ask a person something, or it failed. Before `'flowchart-v1'` there
|
|
87
|
+
* was nowhere to keep a paused run, so the middle one was delivered through
|
|
88
|
+
* `fail` — an error standing in for unfinished work. It is a terminal of its own
|
|
89
|
+
* now, and a pause is never reported as a failure again.
|
|
63
90
|
*/
|
|
64
91
|
export interface HostReply {
|
|
65
92
|
/** Deliver the final answer and end the reply. */
|
|
66
93
|
complete(output: string): void;
|
|
94
|
+
/**
|
|
95
|
+
* End the reply with **unfinished work**: the run stopped to ask a person
|
|
96
|
+
* something, the paused run is stored, and a later request carrying
|
|
97
|
+
* {@link HostRequest.decision} continues it.
|
|
98
|
+
*
|
|
99
|
+
* This is not a failure and must not be reported as one. The agent did not
|
|
100
|
+
* break, no work was lost, and there is nothing to retry — there is a question
|
|
101
|
+
* outstanding. An adapter that maps this onto a 5xx, an error counter or a
|
|
102
|
+
* dead-letter queue is telling every dashboard it feeds something that is not
|
|
103
|
+
* true.
|
|
104
|
+
*
|
|
105
|
+
* Optional on the TYPE for the same reason {@link HostReply.emit} is: a
|
|
106
|
+
* minimal adapter need not implement it. Every shipped adapter does. When it
|
|
107
|
+
* is absent the composer still STORES the paused run — the store is not the
|
|
108
|
+
* transport's business — and ends the reply with a named refusal instead, so
|
|
109
|
+
* the pause is never lost merely because the wire could not describe it.
|
|
110
|
+
*/
|
|
111
|
+
awaiting?(pending: PendingAsk): void;
|
|
67
112
|
/**
|
|
68
113
|
* A piece of the answer, as it is produced.
|
|
69
114
|
*
|
|
@@ -114,37 +159,156 @@ export interface AgentHost {
|
|
|
114
159
|
serve(handler: HostHandler): Promise<HostHandle>;
|
|
115
160
|
}
|
|
116
161
|
/**
|
|
117
|
-
* A
|
|
162
|
+
* A session packed for storage.
|
|
118
163
|
*
|
|
119
164
|
* `format` names WHAT is inside, so a reader that does not know the shape
|
|
120
|
-
* refuses BY NAME instead of restoring a
|
|
165
|
+
* refuses BY NAME instead of restoring a session it cannot actually read.
|
|
121
166
|
* Formats are ADDED, never redefined: an old runtime meeting a new format says
|
|
122
167
|
* so and stops, which is the only safe thing it can do with a payload it cannot
|
|
123
|
-
* interpret.
|
|
124
|
-
*
|
|
125
|
-
* `'conversation-v1'`
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
168
|
+
* interpret. Two exist:
|
|
169
|
+
*
|
|
170
|
+
* • `'conversation-v1'` — a conversation and only a conversation. Every turn
|
|
171
|
+
* that ran to an answer stores this.
|
|
172
|
+
* • `'flowchart-v1'` — a run that stopped mid-flow to ask a person something:
|
|
173
|
+
* the engine's own checkpoint, the conversation as of the pause, and the
|
|
174
|
+
* outstanding ask. 7.14 shipped the version field for exactly this day, and
|
|
175
|
+
* said so; this is that day.
|
|
176
|
+
*
|
|
177
|
+
* The union is discriminated on `format`, so a reader that switches on it is
|
|
178
|
+
* exhaustive by construction and a third format tomorrow breaks the switch at
|
|
179
|
+
* compile time rather than at 3am.
|
|
131
180
|
*/
|
|
132
|
-
export
|
|
181
|
+
export type CheckpointEnvelope = ConversationEnvelope | PausedRunEnvelope;
|
|
182
|
+
/** A conversation packed for storage — what a turn that ANSWERED leaves behind. */
|
|
183
|
+
export interface ConversationEnvelope {
|
|
133
184
|
/** Names the shape of `data`. Unknown values are refused, never guessed at. */
|
|
134
185
|
readonly format: 'conversation-v1';
|
|
135
|
-
/** The conversation itself
|
|
186
|
+
/** The conversation itself. */
|
|
136
187
|
readonly data: AgentRunCheckpoint;
|
|
137
188
|
/** Wall-clock when it was packed. Diagnostic. */
|
|
138
189
|
readonly savedAt: number;
|
|
139
190
|
}
|
|
191
|
+
/** A paused run packed for storage — what a turn that ASKED leaves behind. */
|
|
192
|
+
export interface PausedRunEnvelope {
|
|
193
|
+
/** Names the shape of `data`. Unknown values are refused, never guessed at. */
|
|
194
|
+
readonly format: 'flowchart-v1';
|
|
195
|
+
/** The paused run. */
|
|
196
|
+
readonly data: PausedRun;
|
|
197
|
+
/** Wall-clock when it was packed. Diagnostic. */
|
|
198
|
+
readonly savedAt: number;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* A run that stopped to ask a person something, in the three pieces a session
|
|
202
|
+
* actually needs: what continues it, what it has said so far, and what it is
|
|
203
|
+
* waiting on.
|
|
204
|
+
*
|
|
205
|
+
* ── JSON, honestly ───────────────────────────────────────────────────────────
|
|
206
|
+
* A `FlowchartCheckpoint` is **JSON-safe to resume from, and not byte-identical
|
|
207
|
+
* through JSON.** `JSON.stringify` drops any property whose value is
|
|
208
|
+
* `undefined`, and a real paused agent run has a dozen of them. Every one
|
|
209
|
+
* measured sits in `executionTree` / `subflowResults` — the diagnostic halves
|
|
210
|
+
* the engine keeps for narrative and BTS. `sharedState`, which is the half
|
|
211
|
+
* `agent.resume()` actually reads, round-trips unchanged, because footprintjs's
|
|
212
|
+
* TypedScope already JSON-round-trips every object write on its way into
|
|
213
|
+
* committed state.
|
|
214
|
+
*
|
|
215
|
+
* So: store it anywhere that speaks JSON and resume works. Do not assert that
|
|
216
|
+
* what came back deep-equals what went in — `key: undefined` comes back as no
|
|
217
|
+
* key at all, and a test written to expect otherwise is testing `JSON`, not
|
|
218
|
+
* this library.
|
|
219
|
+
*/
|
|
220
|
+
export interface PausedRun {
|
|
221
|
+
/** The engine checkpoint — everything `agent.resume(checkpoint, decision)` needs. */
|
|
222
|
+
readonly checkpoint: FlowchartCheckpoint;
|
|
223
|
+
/**
|
|
224
|
+
* The conversation as of the pause, in the same shape every other turn stores.
|
|
225
|
+
*
|
|
226
|
+
* Kept alongside the checkpoint so a session that is waiting on a person is
|
|
227
|
+
* still a readable conversation: a support view can show what was said, and a
|
|
228
|
+
* runtime that cannot resume this run can still see the turn that led to the
|
|
229
|
+
* question.
|
|
230
|
+
*/
|
|
231
|
+
readonly conversation: AgentRunCheckpoint;
|
|
232
|
+
/** What the run is waiting on, as data. */
|
|
233
|
+
readonly pending: PendingAsk;
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* The question a paused run is waiting on — the part of a pause that is safe to
|
|
237
|
+
* hand to whoever asked.
|
|
238
|
+
*
|
|
239
|
+
* **It deliberately carries no checkpoint.** The engine checkpoint holds the
|
|
240
|
+
* entire shared state of the run: the system prompt, the whole conversation,
|
|
241
|
+
* every tool result. That belongs in the store, which the operator chose and
|
|
242
|
+
* controls, and not in a reply to whoever posted the request. The caller gets
|
|
243
|
+
* the question; the store gets the state.
|
|
244
|
+
*/
|
|
245
|
+
export interface PendingAsk {
|
|
246
|
+
/** The session holding the paused run — where the decision has to be sent back. */
|
|
247
|
+
readonly sessionId?: string;
|
|
248
|
+
/** The tool that asked, when the run recorded which one it was. */
|
|
249
|
+
readonly tool?: string;
|
|
250
|
+
/** The question in plain words, when the pause carried one. */
|
|
251
|
+
readonly question?: string;
|
|
252
|
+
/**
|
|
253
|
+
* Present ONLY when a tool declared `checkIn` — the typed ask plus its
|
|
254
|
+
* evidence pack (what the tool will do, what context the run read, which
|
|
255
|
+
* context drove the choice, the run so far). Answer with `checkInApproved()`
|
|
256
|
+
* / `checkInDeclined()`.
|
|
257
|
+
*/
|
|
258
|
+
readonly checkIn?: CheckInRequest;
|
|
259
|
+
/**
|
|
260
|
+
* Present ONLY when a `toolMiddleware` answered `ask` — the question and the
|
|
261
|
+
* middleware that put it. Answered with the same decision vocabulary a
|
|
262
|
+
* check-in uses, deliberately: a person approving is a person approving.
|
|
263
|
+
*/
|
|
264
|
+
readonly ask?: MiddlewareAsk;
|
|
265
|
+
/**
|
|
266
|
+
* Exactly what the tool passed to `askHuman()` / `pauseHere()`, uninterpreted.
|
|
267
|
+
* For a plain pause this is the whole of what the tool's author chose to say,
|
|
268
|
+
* and the library is not entitled to summarise it.
|
|
269
|
+
*/
|
|
270
|
+
readonly pauseData: unknown;
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* How often a run's progress is written to the session store — the trade
|
|
274
|
+
* between latency and how much a crash can cost you.
|
|
275
|
+
*
|
|
276
|
+
* - `'exit'` (default) — one write, when the run finishes. The behaviour every
|
|
277
|
+
* release before 7.19 had, spelled out rather than implied. A crash mid-run
|
|
278
|
+
* loses the whole turn.
|
|
279
|
+
* - `'async'` — a write is STARTED whenever the conversation changes and never
|
|
280
|
+
* waited on. The run never slows down; the store is behind by however much
|
|
281
|
+
* the newest un-landed write carried. At most one write is in flight and the
|
|
282
|
+
* newest snapshot supersedes any queued one, so what a crash leaves is always
|
|
283
|
+
* a PREFIX of the run, never a mixture.
|
|
284
|
+
* - `'sync'` — persist-then-proceed. The same trigger, but **iteration N's
|
|
285
|
+
* tools do not execute until iteration N-1's write has landed**, and the
|
|
286
|
+
* answer is not delivered until the last write has landed. You pay the
|
|
287
|
+
* store's latency once per iteration, knowingly, and in exchange the amount
|
|
288
|
+
* of work a crash can re-run has a number: **the current iteration, and
|
|
289
|
+
* nothing before it.**
|
|
290
|
+
*
|
|
291
|
+
* ── The bound, stated exactly ────────────────────────────────────────────────
|
|
292
|
+
* A commit boundary is a whole stage, and the agent dispatches ALL of one
|
|
293
|
+
* iteration's tool calls inside one stage body. So under `'sync'` a crash
|
|
294
|
+
* re-executes the tools of the iteration that was in flight — never an earlier
|
|
295
|
+
* one. That is the same idempotency requirement `resumeOnError` has always
|
|
296
|
+
* carried, now with a boundary instead of a warning: mutating tools must be
|
|
297
|
+
* idempotent, keyed on stable call content rather than `ctx.toolCallId`.
|
|
298
|
+
*/
|
|
299
|
+
export type DurabilityMode = 'exit' | 'async' | 'sync';
|
|
140
300
|
/**
|
|
141
301
|
* Why a session is being woken.
|
|
142
302
|
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
303
|
+
* - `'invoke'` — a request arrived for that session.
|
|
304
|
+
* - `'resume'` — that request carries a person's decision for a run which
|
|
305
|
+
* paused earlier.
|
|
306
|
+
*
|
|
307
|
+
* `'resume'` was absent until 7.19 because nothing could produce it: naming
|
|
308
|
+
* reasons nothing fires would be an interface describing a system that does not
|
|
309
|
+
* exist. Something produces it now.
|
|
146
310
|
*/
|
|
147
|
-
export type WakeReason = 'invoke';
|
|
311
|
+
export type WakeReason = 'invoke' | 'resume';
|
|
148
312
|
/**
|
|
149
313
|
* The port: where a conversation lives between requests.
|
|
150
314
|
*
|
|
@@ -203,4 +367,14 @@ export interface StandingAgentOptions<TH extends HostHandle = HostHandle> {
|
|
|
203
367
|
};
|
|
204
368
|
/** Default `'reject'`. See {@link ConcurrentInvokePolicy}. */
|
|
205
369
|
readonly onConcurrentInvoke?: ConcurrentInvokePolicy;
|
|
370
|
+
/**
|
|
371
|
+
* How often a run's progress becomes crash-survivable. Default `'exit'` —
|
|
372
|
+
* one write when the run finishes, which is what every release before this
|
|
373
|
+
* one did. See {@link DurabilityMode} for what the other two buy and cost.
|
|
374
|
+
*
|
|
375
|
+
* Under `'exit'` nothing is attached to the agent at all: no observer, no
|
|
376
|
+
* per-commit work, no barrier. An agent served this way behaves and performs
|
|
377
|
+
* exactly as it did in 7.18.
|
|
378
|
+
*/
|
|
379
|
+
readonly durability?: DurabilityMode;
|
|
206
380
|
}
|