@agentswf/contract 0.0.0-stage → 0.1.0-beta.1
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/package.json +32 -4
- package/src/call.ts +6 -0
- package/src/index.ts +4 -0
- package/src/participant.ts +15 -0
- package/src/records.ts +893 -0
- package/src/records.typecheck.ts +5 -0
- package/src/schema.ts +342 -0
- package/src/semantic.ts +15 -0
- package/src/testing/fixtures.ts +11 -0
- package/src/wire.ts +578 -0
- package/src/workflow/agents.ts +427 -0
- package/src/workflow/composition.ts +18 -0
- package/src/workflow/deadlines.typecheck.ts +76 -0
- package/src/workflow/decisions.ts +176 -0
- package/src/workflow/decisions.typecheck.ts +97 -0
- package/src/workflow/executable.ts +87 -0
- package/src/workflow/index.ts +10 -0
- package/src/workflow/json.ts +74 -0
- package/src/workflow/messaging.ts +31 -0
- package/src/workflow/messaging.typecheck.ts +114 -0
- package/src/workflow/participants.ts +33 -0
- package/src/workflow/sandboxes.ts +87 -0
- package/src/workflow/sandboxes.typecheck.ts +64 -0
- package/src/workflow/timing.ts +14 -0
- package/src/workflow/workflow.ts +128 -0
- package/README.md +0 -3
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue, OutputSchema } from "./json";
|
|
2
|
+
import type { ParticipantRef } from "./participants";
|
|
3
|
+
import type { InlineSandboxSpec, SandboxRef } from "./sandboxes";
|
|
4
|
+
import type { AbsoluteDeadline } from "./timing";
|
|
5
|
+
|
|
6
|
+
export type AgentKey = string;
|
|
7
|
+
/**
|
|
8
|
+
* Where a skill comes from, as a record keeps it; its name is the one its `SKILL.md` gives. A skill
|
|
9
|
+
* is code its harness runs, so every agent gets a copy of its own, never the source.
|
|
10
|
+
*/
|
|
11
|
+
export type SkillSource =
|
|
12
|
+
/** A directory holding a `SKILL.md`, by its absolute path. */
|
|
13
|
+
| { path: string }
|
|
14
|
+
/**
|
|
15
|
+
* A public skill, as skills.sh names one: `repo` is `owner/repo` on GitHub or a git URL, `skill`
|
|
16
|
+
* the name in its `SKILL.md`, at `ref` or, absent, the default branch when the run starts.
|
|
17
|
+
*/
|
|
18
|
+
| { repo: string; skill: string; ref?: string };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A source as a workflow names it: a path may also be a `file:` URL. One beside the workflow is
|
|
22
|
+
* `new URL("./skills/name", import.meta.url)`, which stays right when another workflow calls it; a
|
|
23
|
+
* relative path is refused, as it would not.
|
|
24
|
+
*/
|
|
25
|
+
export type SkillRef = SkillSource | { path: URL };
|
|
26
|
+
export type RuntimeAliasName = string;
|
|
27
|
+
export type TurnId = string;
|
|
28
|
+
export type CompactionId = string;
|
|
29
|
+
export type HarnessKind = string;
|
|
30
|
+
export type SettingsId = string;
|
|
31
|
+
/** A harness's own level name, such as claude's `max` or codex's `xhigh`; see its definition's `effort`. */
|
|
32
|
+
export type Effort = string;
|
|
33
|
+
|
|
34
|
+
/** What an alias names: today a harness, a model and an effort; later perhaps a pool or sandbox. */
|
|
35
|
+
export type RuntimeTarget = {
|
|
36
|
+
harness: HarnessKind;
|
|
37
|
+
model: string;
|
|
38
|
+
/** Absent, awf passes none and the harness uses its default. */
|
|
39
|
+
effort?: Effort;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Where an agent runs: a terminal pane it keeps between turns, or a headless process per turn.
|
|
44
|
+
* Both answer through the same result channel, under the same deadlines and cleanup.
|
|
45
|
+
*/
|
|
46
|
+
export type AgentPlacement = "pane" | "headless";
|
|
47
|
+
|
|
48
|
+
export type PlacementChoice = {
|
|
49
|
+
/** Defaults to `pane`. */
|
|
50
|
+
placement?: AgentPlacement;
|
|
51
|
+
/**
|
|
52
|
+
* The author's consent to per-token billing, needed to run headless a harness that bills that
|
|
53
|
+
* way even on a subscription login, as `claude -p` does (E3). The run host refuses such an agent
|
|
54
|
+
* without it. It says nothing about how an agent was billed: see `Billing`. It is dropped from a
|
|
55
|
+
* pane agent, where it means nothing.
|
|
56
|
+
*/
|
|
57
|
+
metered?: true;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export function placementOf(choice: PlacementChoice): AgentPlacement {
|
|
61
|
+
return choice.placement ?? "pane";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export type ExecutionConfig = RuntimeTarget & PlacementChoice;
|
|
65
|
+
|
|
66
|
+
/** A centrally configured name for a runtime target. Placement is the agent's choice. */
|
|
67
|
+
export type RuntimeAliases = Readonly<Record<RuntimeAliasName, RuntimeTarget>>;
|
|
68
|
+
|
|
69
|
+
export type ExecutionRequirements = PlacementChoice & {
|
|
70
|
+
/**
|
|
71
|
+
* Fields the alias names (`harness`, `model`) are constraints: when supplied they must match
|
|
72
|
+
* it. Fields the agent owns (`placement`, `metered`) are added to it. Reopening an agent, they
|
|
73
|
+
* are constraints too: left out, the agent stays as it was opened.
|
|
74
|
+
*/
|
|
75
|
+
alias: RuntimeAliasName;
|
|
76
|
+
harness?: HarnessKind;
|
|
77
|
+
model?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Replaces the alias's effort rather than having to match it, unlike `model`: `set` could change
|
|
80
|
+
* it straight after the agent opens anyway. Given on a reopen, it must match the effort the agent
|
|
81
|
+
* was opened at; left out, it does not constrain, as `model` does not.
|
|
82
|
+
*/
|
|
83
|
+
effort?: Effort;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/** An alias, constrained alias, or complete execution configuration. */
|
|
87
|
+
export type RuntimeSelection = RuntimeAliasName | ExecutionRequirements | ExecutionConfig;
|
|
88
|
+
|
|
89
|
+
export type AgentExecution = ExecutionConfig & {
|
|
90
|
+
/**
|
|
91
|
+
* Present when this execution was selected through an alias: the one it was opened through, whose
|
|
92
|
+
* model a `set` may since have changed.
|
|
93
|
+
*/
|
|
94
|
+
alias?: RuntimeAliasName;
|
|
95
|
+
/**
|
|
96
|
+
* The session the run was started from with `awf run --here`, which the run found rather than
|
|
97
|
+
* opened (ADR 0010). Its `model` is `""`: the operator chose it, and spend records carry it.
|
|
98
|
+
*/
|
|
99
|
+
caller?: true;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export interface AgentOpenSpec {
|
|
103
|
+
/** Logical identity scoped to the current workflow run. */
|
|
104
|
+
key: AgentKey;
|
|
105
|
+
/** Bounds this operation; defaults to the current workflow scope deadline. */
|
|
106
|
+
deadline?: AbsoluteDeadline;
|
|
107
|
+
/** Defaults to the workflow's working directory. */
|
|
108
|
+
cwd?: string;
|
|
109
|
+
instructions?: string;
|
|
110
|
+
/** Selects the harness and model. */
|
|
111
|
+
runtime: RuntimeSelection;
|
|
112
|
+
/**
|
|
113
|
+
* Exactly the skills this agent has, beside what its harness will not give up: claude's bundled
|
|
114
|
+
* skills, and claude's and codex's working directory's own; pi keeps nothing else. Absent, it has
|
|
115
|
+
* what it would have without awf: the operator's on the host, none in a sandbox.
|
|
116
|
+
*/
|
|
117
|
+
skills?: readonly SkillRef[];
|
|
118
|
+
labels?: JsonObject;
|
|
119
|
+
/**
|
|
120
|
+
* The sandbox this agent runs in: one this run opened, shared with its other agents, or a spec
|
|
121
|
+
* for a private one. Absent, the agent runs unsandboxed.
|
|
122
|
+
*/
|
|
123
|
+
sandbox?: SandboxRef | InlineSandboxSpec;
|
|
124
|
+
/** Where its pane appears; absent, a tab of its own in the run's workspace. Pane agents only. */
|
|
125
|
+
layout?: PaneLayout;
|
|
126
|
+
/** Whether its pane stays once it is done; absent, `never`. Pane agents only. */
|
|
127
|
+
keepPane?: KeepPane;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Where a pane agent's pane is placed, once, as it opens: a new tab, or beside the pane of an agent
|
|
132
|
+
* already open, which it splits. Where it can't be used, the agent gets a tab of its own instead,
|
|
133
|
+
* and the run says why; it never fails the agent. See `docs/design/pane-layout.md`.
|
|
134
|
+
*/
|
|
135
|
+
export type PaneLayout =
|
|
136
|
+
| {
|
|
137
|
+
/** The terminal session; absent, the run's. One awf does not start is used only if running. */
|
|
138
|
+
session?: string;
|
|
139
|
+
/** Where the new tab opens in it; absent, `"run"`. */
|
|
140
|
+
workspace?: PaneWorkspace;
|
|
141
|
+
/** The new tab's label; absent, the agent's key. Every `tab` is a new tab. */
|
|
142
|
+
tab?: string;
|
|
143
|
+
beside?: never;
|
|
144
|
+
side?: never;
|
|
145
|
+
share?: never;
|
|
146
|
+
}
|
|
147
|
+
| {
|
|
148
|
+
/** Another agent of this run, whose pane this one splits. */
|
|
149
|
+
beside: AgentKey;
|
|
150
|
+
/** The side of that pane the new one goes on. */
|
|
151
|
+
side: "right" | "below";
|
|
152
|
+
/** The new pane's part of the space it splits, 0.2 to 0.8; absent, 0.5. */
|
|
153
|
+
share?: number;
|
|
154
|
+
session?: never;
|
|
155
|
+
workspace?: never;
|
|
156
|
+
tab?: never;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The run's own workspace, the one `awf run` was typed in, or the one with this label, found or
|
|
161
|
+
* made.
|
|
162
|
+
*/
|
|
163
|
+
export type PaneWorkspace = "run" | "origin" | { name: string };
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Whether a pane stays once its agent is done; `on-failure`, when its last operation was not
|
|
167
|
+
* answered. A kept pane keeps its harness running, released from the run: its result channel is
|
|
168
|
+
* closed, and it is the operator's from then on.
|
|
169
|
+
*/
|
|
170
|
+
export type KeepPane = "never" | "on-failure" | "always";
|
|
171
|
+
|
|
172
|
+
interface AgentTurnBase {
|
|
173
|
+
prompt: string;
|
|
174
|
+
/** Bounds this turn, including waiting for an accepted result. */
|
|
175
|
+
deadline: AbsoluteDeadline;
|
|
176
|
+
label?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export interface NudgeOptions {
|
|
180
|
+
/** Bounds presentation and settlement of the continuation. */
|
|
181
|
+
deadline: AbsoluteDeadline;
|
|
182
|
+
/** Uses the engine's standard missing-answer recovery prompt when omitted. */
|
|
183
|
+
prompt?: string;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
interface EnqueuedTurnBase extends AgentTurnBase {
|
|
187
|
+
/** Idempotency key scoped to this agent. Reusing it with a different spec rejects. */
|
|
188
|
+
id: TurnId;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export interface AgentTextTurnSpec extends EnqueuedTurnBase {
|
|
192
|
+
schema?: undefined;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export interface AgentStructuredTurnSpec<T extends JsonValue> extends EnqueuedTurnBase {
|
|
196
|
+
schema: OutputSchema<T>;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
interface AgentRunBase {
|
|
200
|
+
/** Idempotency key scoped to this agent. Generated when omitted. */
|
|
201
|
+
id?: TurnId;
|
|
202
|
+
prompt: string;
|
|
203
|
+
/** Fixed before queueing; defaults to the current scope and covers all automatic check-ins. */
|
|
204
|
+
deadline?: AbsoluteDeadline;
|
|
205
|
+
/** Relative bound from invocation, capped by the current scope; waiting never extends it. */
|
|
206
|
+
timeoutMs?: number;
|
|
207
|
+
label?: string;
|
|
208
|
+
/**
|
|
209
|
+
* Enables automatic recovery, with repeated check-ins where delivery is confirmed.
|
|
210
|
+
* `false` disables it. A supplied deadline can shorten the operation, never extend it.
|
|
211
|
+
*/
|
|
212
|
+
nudge?: false | (Omit<NudgeOptions, "deadline"> & { deadline?: AbsoluteDeadline });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface AgentRunTextSpec extends AgentRunBase {
|
|
216
|
+
schema?: undefined;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface AgentRunStructuredSpec<T extends JsonValue> extends AgentRunBase {
|
|
220
|
+
schema: OutputSchema<T>;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** A harness's own session, as it names it: an id, or for some harnesses a file path. */
|
|
224
|
+
export type NativeSessionRef = { harness: HarnessKind; id: string };
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* An operation's usage while the run goes on: its times and sessions. What it spent is read once
|
|
228
|
+
* the run has ended, into a `SettledOperation` record.
|
|
229
|
+
*/
|
|
230
|
+
export type OperationRecord = {
|
|
231
|
+
/** Nested workflow call ids from outermost to innermost; empty at the root. */
|
|
232
|
+
callPath: string[];
|
|
233
|
+
agent: AgentKey;
|
|
234
|
+
operationId: string;
|
|
235
|
+
/** Resolved execution for this operation. */
|
|
236
|
+
execution: AgentExecution;
|
|
237
|
+
/** ISO time the harness accepted the first attempt; absent when none was made. */
|
|
238
|
+
deliveredAt?: string;
|
|
239
|
+
/** ISO time the operation settled, after any automatic check-ins; its deadline when it expired. */
|
|
240
|
+
settledAt?: string;
|
|
241
|
+
/** Every native session seen for this agent by the time this record was made. */
|
|
242
|
+
sessions: NativeSessionRef[];
|
|
243
|
+
/** The workflow stage it ran in; absent between stages. */
|
|
244
|
+
stage?: string;
|
|
245
|
+
/** The turn's own label, as the workflow gave it. */
|
|
246
|
+
label?: string;
|
|
247
|
+
/** Why it ran: absent for the workflow's own operations. */
|
|
248
|
+
cause?: OperationCause;
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Why an operation ran that the workflow did not start: a message from `from`, woken through a
|
|
253
|
+
* route, which needs no result.
|
|
254
|
+
*/
|
|
255
|
+
export type OperationCause = { message: { from: AgentKey } };
|
|
256
|
+
|
|
257
|
+
/** A harness that needs the operator to log in again. */
|
|
258
|
+
export type TurnLogin = {
|
|
259
|
+
harness: string;
|
|
260
|
+
/** The provider whose login it is, where the harness logs in to several, as pi does. */
|
|
261
|
+
provider?: string;
|
|
262
|
+
/** What the operator runs, in words to show, not parse: "run `pi`, then /login". */
|
|
263
|
+
run: string;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
export type TurnOutcome<T extends JsonValue> = (
|
|
267
|
+
| { kind: "answered"; value: T }
|
|
268
|
+
| { kind: "unanswered"; reason: string }
|
|
269
|
+
| { kind: "blocked"; reason: string }
|
|
270
|
+
| { kind: "timed-out"; reason: string }
|
|
271
|
+
| {
|
|
272
|
+
kind: "failed";
|
|
273
|
+
reason: string;
|
|
274
|
+
retryable: boolean;
|
|
275
|
+
/**
|
|
276
|
+
* Set when the agent's harness has no login, or its login was refused: every agent on it
|
|
277
|
+
* fails the same way until the operator logs in, so a workflow may stop here.
|
|
278
|
+
*/
|
|
279
|
+
login?: TurnLogin;
|
|
280
|
+
}
|
|
281
|
+
| { kind: "cancelled"; reason: string }
|
|
282
|
+
) & {
|
|
283
|
+
/** Times and sessions across every delivery attempt made to settle this operation. */
|
|
284
|
+
usage: OperationRecord;
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export function isAnswered<T extends JsonValue>(
|
|
288
|
+
outcome: TurnOutcome<T>,
|
|
289
|
+
): outcome is Extract<TurnOutcome<T>, { kind: "answered" }> {
|
|
290
|
+
return outcome.kind === "answered";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
export type RunResult<T extends JsonValue> = {
|
|
294
|
+
/** The whole operation outcome, including automatic check-ins and bounded native release. */
|
|
295
|
+
outcome: TurnOutcome<T>;
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export interface TurnRef<T extends JsonValue> {
|
|
299
|
+
readonly id: TurnId;
|
|
300
|
+
/** Resolves to an explicit terminal state; an absent agent answer is never successful data. */
|
|
301
|
+
readonly result: Promise<TurnOutcome<T>>;
|
|
302
|
+
/**
|
|
303
|
+
* Makes one additional delivery attempt against this turn's existing result slot and authority.
|
|
304
|
+
* Repeating the same nudge returns the same ref; a different nudge or later queued operation
|
|
305
|
+
* rejects.
|
|
306
|
+
*/
|
|
307
|
+
nudge(options: NudgeOptions): Promise<TurnRef<T>>;
|
|
308
|
+
/** Returns false when cancellation is unsupported or the turn is already terminal. */
|
|
309
|
+
cancel(reason?: string): Promise<boolean>;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface CompactSpec {
|
|
313
|
+
/** Idempotency key scoped to this agent. Generated when omitted. */
|
|
314
|
+
id?: CompactionId;
|
|
315
|
+
/** What the harness's own compaction keeps and drops, as an operator types after `/compact`. */
|
|
316
|
+
prompt: string;
|
|
317
|
+
/** Bounds this operation; defaults to the current workflow scope deadline. */
|
|
318
|
+
deadline?: AbsoluteDeadline;
|
|
319
|
+
/** Relative bound from invocation, capped by the current workflow scope deadline. */
|
|
320
|
+
timeoutMs?: number;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export interface AgentForkSpec extends PlacementChoice {
|
|
324
|
+
/** The new agent's key in this run. */
|
|
325
|
+
key: AgentKey;
|
|
326
|
+
/** Given with its first turn; it already knows what this agent was told. */
|
|
327
|
+
instructions?: string;
|
|
328
|
+
labels?: JsonObject;
|
|
329
|
+
/** Absent, the fork takes its parent's effort when it is taken, as it takes its model. */
|
|
330
|
+
effort?: Effort;
|
|
331
|
+
/** Never its parent's: absent, a tab of its own. */
|
|
332
|
+
layout?: PaneLayout;
|
|
333
|
+
/** Never its parent's: absent, `never`. */
|
|
334
|
+
keepPane?: KeepPane;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface SettingsSpec {
|
|
338
|
+
/** Idempotency key scoped to this agent. Generated when omitted. */
|
|
339
|
+
id?: SettingsId;
|
|
340
|
+
/** Another model of the same harness. */
|
|
341
|
+
model?: string;
|
|
342
|
+
effort?: Effort;
|
|
343
|
+
/** Bounds this operation; defaults to the current workflow scope deadline. */
|
|
344
|
+
deadline?: AbsoluteDeadline;
|
|
345
|
+
/** Relative bound from invocation, capped by the current workflow scope deadline. */
|
|
346
|
+
timeoutMs?: number;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export interface AgentRef extends ParticipantRef {
|
|
350
|
+
readonly key: AgentKey;
|
|
351
|
+
/**
|
|
352
|
+
* The agent's settings now: as it was opened, then as the last answered `set` left them. Its
|
|
353
|
+
* harness, placement and alias never change.
|
|
354
|
+
*/
|
|
355
|
+
readonly execution: AgentExecution;
|
|
356
|
+
/** Resolves once durably queued. Turns execute one at a time in enqueue order. */
|
|
357
|
+
enqueue(spec: AgentTextTurnSpec): Promise<TurnRef<string>>;
|
|
358
|
+
enqueue<T extends JsonValue>(spec: AgentStructuredTurnSpec<T>): Promise<TurnRef<T>>;
|
|
359
|
+
/**
|
|
360
|
+
* Runs and awaits. Supplied ids cover the complete spec; conflicting reuse rejects. Cancellation
|
|
361
|
+
* by another ref or workflow shutdown resolves as `cancelled`.
|
|
362
|
+
*/
|
|
363
|
+
run(spec: AgentRunTextSpec): Promise<RunResult<string>>;
|
|
364
|
+
run<T extends JsonValue>(spec: AgentRunStructuredSpec<T>): Promise<RunResult<T>>;
|
|
365
|
+
/**
|
|
366
|
+
* Runs the harness's own compaction after earlier operations, idempotent by compaction id. An
|
|
367
|
+
* answer means it compacted, and is its summary where the harness exposes one, else `""`; any
|
|
368
|
+
* other outcome leaves the prior context available.
|
|
369
|
+
*/
|
|
370
|
+
compact(spec: CompactSpec): Promise<TurnOutcome<string>>;
|
|
371
|
+
/**
|
|
372
|
+
* Switches this session's model or effort for every operation after the earlier ones, idempotent
|
|
373
|
+
* by id; its context is kept. Answered once the switch is in force, with the settings then in
|
|
374
|
+
* force as the outcome's `usage.execution`. Refused before it runs where the harness can't switch,
|
|
375
|
+
* with why, and for an effort the harness doesn't list. One that fails or times out in a pane
|
|
376
|
+
* closes the agent, whose settings nobody then knows.
|
|
377
|
+
*/
|
|
378
|
+
set(spec: SettingsSpec): Promise<TurnOutcome<null>>;
|
|
379
|
+
/**
|
|
380
|
+
* Opens a new agent on a copy of this agent's session, taken after its earlier operations: it
|
|
381
|
+
* starts knowing what this agent knew then, and from then on neither sees the other's turns. It
|
|
382
|
+
* has this agent's harness, model, working directory, sandbox and skills, and this agent's
|
|
383
|
+
* placement and effort unless it names its own; model and effort are those after every `set`
|
|
384
|
+
* queued before it. Rejects before this agent's first turn, where the harness cannot fork, and
|
|
385
|
+
* where a `set` queued before it did not take. The same key with the same parent and spec returns the same agent (ADR 0009).
|
|
386
|
+
*/
|
|
387
|
+
fork(spec: AgentForkSpec): Promise<AgentRef>;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface CallerSpec {
|
|
391
|
+
/** The key the calling session goes by in this run, as any agent's does. */
|
|
392
|
+
key: AgentKey;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export interface AgentDirectory {
|
|
396
|
+
/**
|
|
397
|
+
* Reattaches by logical key when every supplied field matches; omitted optional fields do not
|
|
398
|
+
* constrain an existing agent. Runtime requirements are never silently relaxed. A conflicting
|
|
399
|
+
* spec or occupied outside-participant key rejects rather than mutating the existing participant.
|
|
400
|
+
*/
|
|
401
|
+
open(spec: AgentOpenSpec): Promise<AgentRef>;
|
|
402
|
+
/** Returns null when absent; rejects when an existing agent conflicts with the requirements. */
|
|
403
|
+
attach(key: AgentKey, runtime?: RuntimeSelection): Promise<AgentRef | null>;
|
|
404
|
+
/**
|
|
405
|
+
* Ends the agent before the run does: a turn it is running is cancelled, and its pane closed or,
|
|
406
|
+
* where `keepPane` says, kept and released (ADR 0008). Its key is not opened again. Resolves
|
|
407
|
+
* false when the agent was never opened, did not open, or is already stopped.
|
|
408
|
+
*/
|
|
409
|
+
stop(key: AgentKey, reason?: string): Promise<boolean>;
|
|
410
|
+
/**
|
|
411
|
+
* The session `awf run --here` was started from, as an agent under `spec.key`; `null` when the
|
|
412
|
+
* run has none (ADR 0010). The same key returns the same ref and another key rejects. It is the
|
|
413
|
+
* operator's session, so it differs from an opened agent: `compact` fails; a turn that fails, is
|
|
414
|
+
* cancelled or times out leaves it usable rather than closed; the operator interrupting a turn
|
|
415
|
+
* settles it `cancelled`; where the harness's interrupt cannot be recognised, an unanswered turn
|
|
416
|
+
* is not nudged by default; and its `execution.model` is `""`. Its `fork` is `forkCaller`'s.
|
|
417
|
+
*/
|
|
418
|
+
caller(spec: CallerSpec): Promise<AgentRef | null>;
|
|
419
|
+
/**
|
|
420
|
+
* A new agent on a copy of the session `awf run` was started from: one waiting on the command,
|
|
421
|
+
* or handed over with `--here`, whose copy is then taken after the run's turns to it. `null` when
|
|
422
|
+
* the run has none to fork; the run's output says why. The fork runs on the model the session's
|
|
423
|
+
* files show it last ran, in its directory, as an agent of the run; what it copied stays the
|
|
424
|
+
* session's spend. Root scope only (story 027).
|
|
425
|
+
*/
|
|
426
|
+
forkCaller(spec: AgentForkSpec): Promise<AgentRef | null>;
|
|
427
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { JsonValue } from "./json";
|
|
2
|
+
import type { ParticipantKey, ParticipantRef } from "./participants";
|
|
3
|
+
import type { AbsoluteDeadline } from "./timing";
|
|
4
|
+
import type { WorkflowDefinition } from "./workflow";
|
|
5
|
+
|
|
6
|
+
export type WorkflowCallId = string;
|
|
7
|
+
|
|
8
|
+
export interface WorkflowCallSpec<Args extends JsonValue, Result extends JsonValue> {
|
|
9
|
+
/** Idempotency key scoped to the caller. */
|
|
10
|
+
id: WorkflowCallId;
|
|
11
|
+
definition: WorkflowDefinition<Args, Result>;
|
|
12
|
+
args: Args;
|
|
13
|
+
/** Defaults to the current workflow scope deadline. */
|
|
14
|
+
deadline?: AbsoluteDeadline;
|
|
15
|
+
/** Parent participants exposed under child-local keys. */
|
|
16
|
+
participants?: Readonly<Record<ParticipantKey, ParticipantRef>>;
|
|
17
|
+
label?: string;
|
|
18
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AbsoluteDeadline,
|
|
3
|
+
AgentForkSpec,
|
|
4
|
+
AgentOpenSpec,
|
|
5
|
+
AgentRef,
|
|
6
|
+
AgentRunTextSpec,
|
|
7
|
+
CompactSpec,
|
|
8
|
+
ParallelOptions,
|
|
9
|
+
SignalSpec,
|
|
10
|
+
StepSpec,
|
|
11
|
+
TurnOutcome,
|
|
12
|
+
TurnRef,
|
|
13
|
+
WorkflowCallSpec,
|
|
14
|
+
WorkflowContext,
|
|
15
|
+
WorkflowDefinition,
|
|
16
|
+
} from "./index";
|
|
17
|
+
|
|
18
|
+
declare const deadline: AbsoluteDeadline;
|
|
19
|
+
declare const context: WorkflowContext;
|
|
20
|
+
declare const agentRef: AgentRef;
|
|
21
|
+
declare const turnRef: TurnRef<string>;
|
|
22
|
+
declare const definition: WorkflowDefinition<null, string>;
|
|
23
|
+
|
|
24
|
+
const turn = {
|
|
25
|
+
prompt: "Review the change",
|
|
26
|
+
deadline,
|
|
27
|
+
nudge: { deadline },
|
|
28
|
+
} satisfies AgentRunTextSpec;
|
|
29
|
+
|
|
30
|
+
const parallel = { deadline, concurrency: 2 } satisfies ParallelOptions;
|
|
31
|
+
const inheritedTurn = { prompt: "Review the change", nudge: {} } satisfies AgentRunTextSpec;
|
|
32
|
+
const disabledNudge = { prompt: "Review the change", nudge: false } satisfies AgentRunTextSpec;
|
|
33
|
+
const inheritedParallel = { concurrency: 2 } satisfies ParallelOptions;
|
|
34
|
+
const inheritedOpen = { key: "reviewer", runtime: "default" } satisfies AgentOpenSpec;
|
|
35
|
+
const inheritedSignal = { id: "signal", name: "approval" } satisfies SignalSpec;
|
|
36
|
+
const inheritedCall = {
|
|
37
|
+
id: "child",
|
|
38
|
+
definition,
|
|
39
|
+
args: null,
|
|
40
|
+
} satisfies WorkflowCallSpec<null, string>;
|
|
41
|
+
const inheritedStep = { id: "step" } satisfies StepSpec;
|
|
42
|
+
const inheritedCompact = { prompt: "Keep the plan." } satisfies CompactSpec;
|
|
43
|
+
const boundedCompact = { prompt: "Keep the plan.", timeoutMs: 60_000 } satisfies CompactSpec;
|
|
44
|
+
// A fork is bounded by the workflow scope, as an agent's opening is.
|
|
45
|
+
const inheritedFork = { key: "tests" } satisfies AgentForkSpec;
|
|
46
|
+
|
|
47
|
+
function terminalKind(outcome: TurnOutcome<string>): string {
|
|
48
|
+
return outcome.kind === "timed-out" ? outcome.reason : outcome.kind;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function rejectedShapes(): void {
|
|
52
|
+
// @ts-expect-error Automatic recovery accepts options or false, never true.
|
|
53
|
+
const unboundedNudge: AgentRunTextSpec = { prompt: "Review", deadline, nudge: true };
|
|
54
|
+
// @ts-expect-error Enqueued turns are bounded even when not immediately awaited.
|
|
55
|
+
agentRef.enqueue({ id: "review", prompt: "Review" });
|
|
56
|
+
// @ts-expect-error A manual nudge cannot inherit an implicit infinite deadline.
|
|
57
|
+
turnRef.nudge();
|
|
58
|
+
void unboundedNudge;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
void [
|
|
62
|
+
inheritedFork,
|
|
63
|
+
turn,
|
|
64
|
+
parallel,
|
|
65
|
+
inheritedTurn,
|
|
66
|
+
disabledNudge,
|
|
67
|
+
inheritedParallel,
|
|
68
|
+
inheritedOpen,
|
|
69
|
+
inheritedSignal,
|
|
70
|
+
inheritedCall,
|
|
71
|
+
inheritedStep,
|
|
72
|
+
inheritedCompact,
|
|
73
|
+
boundedCompact,
|
|
74
|
+
terminalKind,
|
|
75
|
+
rejectedShapes,
|
|
76
|
+
];
|