agentswf 0.1.0-beta.5 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 the agents.wf authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,14 +1,107 @@
1
1
  # agentswf
2
2
 
3
- agents.wf is an engine that runs workflows made of coding agents. This package gives you the
4
- `awf` command and `agentswf/workflow`, the module a workflow imports.
3
+ **Write workflows for coding agents in TypeScript.** Hand work to the coding agents you already
4
+ use, run them side by side, have one check another, loop until a reviewer is happy, and get typed
5
+ answers back. `awf` runs the workflow and handles the parts that make agent scripts fragile:
6
+ agents that never answer, answer in prose, hang, or quietly run up a bill.
5
7
 
6
- > **Beta.** Interfaces will change between versions.
8
+ ```ts
9
+ const reviewer = await wf.agents.open({ key: "reviewer", runtime: { harness: "codex", model: "gpt-6-luna" } });
10
+ const { outcome } = await reviewer.run({ prompt: "Review `git diff main`.", schema: FINDINGS });
11
+ if (outcome.answered) console.log(outcome.value.findings); // validated against FINDINGS, or a reason why not
12
+ ```
13
+
14
+ > **Shaped by use.** agents.wf is in its evaluation phase, and what people build with it decides
15
+ > where the design goes next. Found a bug, missing a use case, or have an idea?
16
+ > [Open an issue](https://github.com/romanlv/agentswf/issues).
17
+
18
+ ## How it works
19
+
20
+ 1. Your code calls `agent.run({ prompt, schema })`.
21
+ 2. awf hands the prompt to a coding agent, in a [Herdr](https://herdr.dev) pane you can watch or
22
+ headless.
23
+ 3. The agent works in your repo with its own tools and skills, then answers by running
24
+ `wfa result`.
25
+ 4. awf checks the answer against your schema. A wrong shape goes back to the agent to fix; a quiet
26
+ agent gets a nudge.
27
+ 5. `await` returns a typed value, and your code decides what happens next.
28
+
29
+ It drives the agent CLIs you already use, logged in as you already are: claude, codex, pi or
30
+ cursor. Control flow is plain TypeScript: `for`, `if`, `Promise.all`.
31
+
32
+ ## What you get
33
+
34
+ - **Typed answers, checked.** A turn ends `answered`, with JSON that fits its schema, or with a
35
+ reason: `unanswered`, `blocked`, `timed-out`, `failed` or `cancelled`.
36
+ - **Mix harnesses and models.** A codex reviewer checks a claude implementer; a cheap model
37
+ triages before an expensive one works.
38
+ - **Long-lived sessions.** An agent keeps its context from turn to turn; `compact` and `fork` it.
39
+ - **Stages you can continue.** A run that stops part-way goes on from the stage that stopped.
40
+ - **Cost on every run.** Time, tokens and an estimated cost per agent, stage and model.
41
+ - **Sandboxes, messaging, events.** docker or srt limits what agents reach, agents message each
42
+ other on routes you grant, and a run can wait for an event for weeks without a process running.
43
+ - **Free tests.** `agentswf/testing` runs a workflow on scripted agents; no agent starts.
44
+
45
+ ## Install
46
+
47
+ You need [Bun](https://bun.sh) 1.4 or later, git, and at least one agent CLI you're logged in to.
7
48
 
8
49
  ```sh
9
- bun add -g agentswf@beta
10
- awf run ./review.ts
50
+ bun add -g agentswf # puts awf in ~/.bun/bin
51
+ awf --version
11
52
  ```
12
53
 
13
- It runs on Bun only. The README in the repository says what to install first and how to write a
14
- workflow: [github.com/romanlv/agentswf](https://github.com/romanlv/agentswf).
54
+ ## Your first workflow
55
+
56
+ ```sh
57
+ mkdir ~/workflows && cd ~/workflows
58
+ awf init # a hello workflow, its test, and the config files
59
+ awf test # its test, on scripted agents
60
+ awf run hello/workflow.ts # one real agent
61
+ ```
62
+
63
+ ```ts
64
+ // hello/workflow.ts
65
+ import { typed } from "agentswf/typebox";
66
+ import { defineWorkflow } from "agentswf/workflow";
67
+ import Type from "typebox";
68
+
69
+ export const ANSWER = typed(Type.Object({ answer: Type.Integer() }, { additionalProperties: false }));
70
+
71
+ export default defineWorkflow({
72
+ meta: { name: "hello", description: "Ask one agent a question with a known answer." },
73
+ async run(wf) {
74
+ const agent = await wf.agents.open({
75
+ key: "hello",
76
+ runtime: { harness: "codex", model: "gpt-6-luna", placement: "headless" },
77
+ });
78
+ const { outcome } = await agent.run({
79
+ prompt: "What is 17 multiplied by 23? Work it out yourself; do not run any code.",
80
+ schema: ANSWER,
81
+ timeout: "3m",
82
+ });
83
+ return outcome.answered ? outcome.value : { unanswered: outcome.reason };
84
+ },
85
+ });
86
+ ```
87
+
88
+ `awf run` prints a line on time, tokens and estimated cost, then the run's record as JSON, its
89
+ `value` `{ "answer": 391 }`; a workflow's `present` prints something shorter instead. The record
90
+ stays in `.awf/runs/hello/{id}`; `awf runs` and `awf logs` show what it did.
91
+
92
+ ## What this package gives you
93
+
94
+ | Entry | For |
95
+ | --- | --- |
96
+ | `awf` | the command: run, test, follow and continue workflows |
97
+ | `agentswf/workflow` | what a workflow imports: `defineWorkflow`, the `wf` context |
98
+ | `agentswf/typebox` | `typed`, to turn a TypeBox schema into an answer schema |
99
+ | `agentswf/testing` | run a workflow on scripted agents in its tests |
100
+ | `agentswf/client` | drive runs from your own program |
101
+
102
+ ## Learn more
103
+
104
+ - [Guide](https://agents.wf/guide/) and [quick start](https://agents.wf/guide/quick-start)
105
+ - [Workflow API](https://agents.wf/reference/workflow-api), every call on one page
106
+ - [Patterns](https://agents.wf/patterns/review-loop): review loops, plan and implement
107
+ - [Examples and design](https://github.com/romanlv/agentswf), in the repository
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentswf",
3
- "version": "0.1.0-beta.5",
4
- "description": "agents.wf: an engine that runs workflows made of coding agents. Gives you awf, agentswf/workflow and agentswf/testing",
3
+ "version": "0.1.0",
4
+ "description": "agents.wf: an engine that runs workflows made of coding agents. Gives you awf, agentswf/workflow, agentswf/typebox, agentswf/testing and agentswf/client",
5
5
  "license": "MIT",
6
6
  "homepage": "https://agents.wf",
7
7
  "repository": {
@@ -18,15 +18,23 @@
18
18
  },
19
19
  "exports": {
20
20
  "./workflow": "./src/workflow.ts",
21
- "./testing": "./src/testing.ts"
21
+ "./testing": "./src/testing.ts",
22
+ "./typebox": "./src/typebox.ts",
23
+ "./client": "./src/client.ts"
22
24
  },
23
25
  "files": [
24
26
  "src",
25
- "README.md"
27
+ "!src/**/*.test.ts",
28
+ "!src/**/*.eval.ts",
29
+ "!src/**/*.typecheck.ts",
30
+ "!src/**/*.fixture.ts",
31
+ "!src/**/fixtures",
32
+ "README.md",
33
+ "LICENSE"
26
34
  ],
27
35
  "dependencies": {
28
- "@agentswf/contract": "0.1.0-beta.5",
29
- "@agentswf/engine": "0.1.0-beta.5"
36
+ "@agentswf/contract": "0.1.0",
37
+ "@agentswf/engine": "0.1.0"
30
38
  },
31
39
  "publishConfig": {
32
40
  "access": "public"
package/src/awf.ts CHANGED
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env -S bun --no-env-file
2
- import { main } from "@agentswf/engine/cli";
2
+ import { dirname } from "node:path";
3
+ import { handOver } from "@agentswf/engine/hand-over";
4
+ import manifest from "../package.json" with { type: "json" };
3
5
 
6
+ // Before the engine loads: a project at another version runs on its own (ADR 0015).
7
+ await handOver({ dir: dirname(import.meta.dir), version: manifest.version });
8
+ const { main } = await import("@agentswf/engine/cli");
4
9
  await main();
package/src/client.ts ADDED
@@ -0,0 +1,2 @@
1
+ // A program's way to a workflow's runs: the types live in the contract, `client` in the engine.
2
+ export * from "@agentswf/engine/client";
package/src/typebox.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "@agentswf/engine/typebox";
@@ -1,163 +0,0 @@
1
- // Checked by `tsc --noEmit`, never run: what `agentswf/testing` offers a workflow's test, and what
2
- // it doesn't. Each deprecated name compiles until story 039's last task removes it.
3
-
4
- import {
5
- answer,
6
- noAnswer,
7
- reply,
8
- type TestOptions,
9
- type TestRun,
10
- type TestTurn,
11
- type TestTurnOutcome,
12
- testWorkflow,
13
- } from "./testing";
14
- import { AgentEnded, defineWorkflow, type OutputSchema, ParticipantReply } from "./workflow";
15
-
16
- type Surface = typeof import("./testing");
17
- // @ts-expect-error Only a claude TUI agent may wait, which its harness decides, not a workflow.
18
- export type Waiting = Surface["waiting"];
19
- // @ts-expect-error The engine's own tests, which say where an agent may await a reply, call it.
20
- export type RunTest = Surface["runTest"];
21
-
22
- const plain = defineWorkflow({
23
- meta: { name: "plain", description: "One agent." },
24
- run: async (wf) => {
25
- const agent = await wf.agents.open({ key: "a", runtime: "claude" });
26
- return (await agent.run({ prompt: "Go." })).outcome.kind;
27
- },
28
- });
29
- const greet = defineWorkflow<{ name: string }, string>({
30
- meta: { name: "greet", description: "Greets one name." },
31
- prepare: ({ argv }) => ({ name: argv[0] ?? "nobody" }),
32
- run: async (_wf, { name }) => `hi ${name}`,
33
- });
34
- const Ping = { type: "object" } as OutputSchema<{ n: number }>;
35
- const withEvents = defineWorkflow({
36
- meta: { name: "events", description: "Woken by pings." },
37
- file: import.meta.path,
38
- events: { ping: Ping },
39
- run: async (wf) => (await wf.receive()).length,
40
- });
41
- const bare = { meta: plain.meta, run: plain.run };
42
- const maybe = defineWorkflow<{ x: number } | null, number>({
43
- meta: { name: "maybe", description: "Args or none." },
44
- prepare: () => null,
45
- run: async (_wf, args) => args?.x ?? 0,
46
- });
47
- const listed = defineWorkflow<string[], number>({
48
- meta: { name: "listed", description: "Counts." },
49
- prepare: ({ argv }) => [...argv],
50
- run: async (_wf, names) => names.length,
51
- });
52
-
53
- export async function argsShapes(): Promise<void> {
54
- // Null among its args: they may be left out.
55
- await testWorkflow(maybe);
56
- await testWorkflow(maybe, { args: { x: 1 } });
57
- await testWorkflow(listed, { args: ["a"] });
58
- // @ts-expect-error Array args are required too.
59
- await testWorkflow(listed, {});
60
- // Deprecated: array args given second, with no options.
61
- await testWorkflow(listed, ["a", "b"]);
62
- }
63
-
64
- export async function oneSignature(): Promise<void> {
65
- await testWorkflow(plain);
66
- await testWorkflow(plain, { agents: { a: answer("done") }, maxTurns: 3, id: "r-1" });
67
- await testWorkflow(greet, { args: { name: "ada" } });
68
- // @ts-expect-error A workflow that takes args is given them.
69
- await testWorkflow(greet);
70
- // @ts-expect-error A workflow that takes args is given them, among its options.
71
- await testWorkflow(greet, { agents: { a: answer("done") } });
72
- // @ts-expect-error Its args given second are not options: an object there always is.
73
- await testWorkflow(greet, { name: "ada" });
74
- // @ts-expect-error A misspelled option is refused.
75
- await testWorkflow(plain, { agnets: { a: answer("done") } });
76
- // @ts-expect-error A workflow without events is sent none.
77
- await testWorkflow(plain, { event: { name: "ping", data: { n: 1 } } });
78
- // @ts-expect-error The one form tests what defineWorkflow made.
79
- await testWorkflow(bare, {});
80
- await testWorkflow(withEvents, {
81
- earlier: { stages: { plan: null }, mailbox: [] },
82
- event: { name: "ping", data: { n: 1 } },
83
- fromStage: "plan",
84
- });
85
- const shared: TestOptions = { agents: { a: answer("done") } };
86
- await testWorkflow(plain, shared);
87
- await testWorkflow(greet, { ...shared, args: { name: "ada" } });
88
- }
89
-
90
- export async function removed(): Promise<void> {
91
- // @ts-expect-error A wait is the harness's check-in, not a script's ending.
92
- reply.waiting("for CI");
93
- // @ts-expect-error Where an agent may await a reply is the engine's list.
94
- await testWorkflow(plain, { awaitReply: "anywhere" });
95
- // @ts-expect-error A message script ends a turn; it answers no call.
96
- await testWorkflow(plain, { messages: { a: () => answer("done") } });
97
- }
98
-
99
- export async function deprecated(run: TestRun<string>): Promise<void> {
100
- await testWorkflow(plain, null, {
101
- agents: { a: [reply.silent(), reply.blocked(), reply.failed(), reply.timedOut()] },
102
- compactions: { a: [reply.hang(), reply.interrupted(), reply.needsLogin()] },
103
- messages: { a: [(message) => message.silent(), (message) => message.fail("crashed")] },
104
- recorded: { plan: null },
105
- });
106
- await testWorkflow(greet, { name: "ada" }, undefined);
107
- await testWorkflow(plain, null);
108
- await testWorkflow(bare, null, {});
109
- await testWorkflow(withEvents, {
110
- id: "k",
111
- event: { name: "ping", data: { n: 1 }, id: "e-1" },
112
- events: [{ during: "a#1", name: "ping", data: { n: 2 }, id: "e-2" }],
113
- mailbox: [],
114
- });
115
- void run.stopped?.reason;
116
- void run.parked?.label;
117
- }
118
-
119
- export async function current(): Promise<void> {
120
- const run = await testWorkflow(plain, {
121
- agents: { a: [noAnswer.silent(), noAnswer.failed("crashed"), answer("done")] },
122
- messages: {
123
- a: [() => {}, async (message) => message.reply("Done."), () => noAnswer.failed("crashed")],
124
- },
125
- });
126
- const turn: TestTurn = run.turns[0]!;
127
- const outcome: TestTurnOutcome = turn.outcome;
128
- // @ts-expect-error A turn records the engine's word, unanswered, not the script's.
129
- const silent: TestTurnOutcome = "silent";
130
- void [outcome, silent];
131
- if (run.ending.kind === "stopped") void [run.ending.stage, run.ending.reason];
132
- if (run.ending.kind === "completed") void run.ending.value;
133
- await testWorkflow(withEvents, {
134
- id: "k",
135
- event: { name: "ping", data: { n: 1 }, eventId: "e-1" },
136
- events: [{ during: "a#1", name: "ping", data: { n: 2 }, eventId: "e-2" }],
137
- });
138
- }
139
-
140
- const declaring = defineWorkflow({
141
- meta: { name: "declaring", description: "Declares its thread's replies and an agent's end." },
142
- file: import.meta.path,
143
- events: { ping: Ping, "thread:reply": ParticipantReply, "a:ended": AgentEnded },
144
- run: async (wf) => (await wf.receive()).length,
145
- });
146
-
147
- export async function scriptedReplies(): Promise<void> {
148
- await testWorkflow(declaring, {
149
- events: [{ during: "a#1", name: "thread:reply", data: { text: "hi", author: "dana" } }],
150
- });
151
- // @ts-expect-error A reply's data is ParticipantReply's: text, and author.
152
- await testWorkflow(declaring, {
153
- events: [{ during: "a#1", name: "thread:reply", data: { body: "hi" } }],
154
- });
155
- // @ts-expect-error Only awf sends an agent's end.
156
- await testWorkflow(declaring, {
157
- events: [{ during: "a#1", name: "a:ended", data: { turn: "t", meta: null } }],
158
- });
159
- // Deprecated: the old name, from the participant's key.
160
- await testWorkflow(withEvents, {
161
- events: [{ during: "a#1", name: "participant:reply", from: "thread", data: { text: "hi" } }],
162
- });
163
- }
@@ -1,309 +0,0 @@
1
- // Checked by `tsc --noEmit`, never run: what `agentswf/workflow` offers an author, and what it
2
- // doesn't. Each deprecated name compiles until story 039's last task removes it.
3
-
4
- import {
5
- type AgentDirectory,
6
- AgentEnded,
7
- type AgentEndedEvent,
8
- type AgentOpenSpec,
9
- type AgentRef,
10
- type CallerRef,
11
- type CodeParticipantRef,
12
- type CodeParticipantSpec,
13
- type Duration,
14
- defineExecutableWorkflow,
15
- defineWorkflow,
16
- duration,
17
- type EventAgentDirectory,
18
- type EventAgentOpenSpec,
19
- type EventAgentRef,
20
- type EventWorkflowContext,
21
- isAnswered,
22
- type JsonValue,
23
- type OutputSchema,
24
- type ParticipantRef,
25
- ParticipantReply,
26
- type ParticipantReplyEvent,
27
- tool,
28
- type WorkflowContext,
29
- type WorkflowEvent,
30
- yesNo,
31
- } from "./workflow";
32
-
33
- type Surface = typeof import("./workflow");
34
- // @ts-expect-error The engine's own check of a workflow's events, not an author's.
35
- export type EventWorkflowProblem = Surface["eventWorkflowProblem"];
36
- // @ts-expect-error The engine reads an agent's placement; an author writes it.
37
- export type PlacementOf = Surface["placementOf"];
38
- // @ts-expect-error Removed: a workflow is a `Workflow`, from `defineWorkflow`.
39
- export type Definition = import("./workflow").WorkflowDefinition<null, null>;
40
- // @ts-expect-error Removed with `wf.call`, which was never built.
41
- export type CallSpec = import("./workflow").WorkflowCallSpec<null, null>;
42
- // @ts-expect-error Removed with `wf.steps`, which was never built.
43
- export type StepSpec = import("./workflow").StepSpec;
44
- // @ts-expect-error One `WaitOptions` for receive, race and select.
45
- export type SelectOptions = import("./workflow").SelectOptions;
46
- // @ts-expect-error One `WaitOptions` for receive, race and select.
47
- export type ReceiveOptions = import("./workflow").ReceiveOptions;
48
-
49
- declare const plain: WorkflowContext;
50
- declare const Changed: OutputSchema<{ head: string }>;
51
- type Events = { changed: typeof Changed };
52
- declare const wf: WorkflowContext<Events>;
53
-
54
- export async function removed(): Promise<void> {
55
- // @ts-expect-error A stage and wf.sleep do what steps would have.
56
- await plain.steps.run({ id: "x" }, async () => null);
57
- // @ts-expect-error One workflow calling another was never built.
58
- await plain.call({ id: "child" });
59
- // @ts-expect-error Opening the same key again returns the agent opened before.
60
- await plain.agents.attach("reviewer");
61
- // @ts-expect-error A participant's ref is what connect returned.
62
- await plain.participants.get("coordinator");
63
- // @ts-expect-error A loop over each timer is a select with each as a branch.
64
- wf.timers();
65
- // @ts-expect-error receive takes every event not yet acknowledged; select takes one at a time.
66
- await wf.receive({ take: "oldest" });
67
- }
68
-
69
- export async function deprecated(): Promise<void> {
70
- const reviewer = await wf.agents.open({ key: "r", runtime: "claude", labels: { lens: "x" } });
71
- await reviewer.fork({ key: "r2", labels: { lens: "y" } });
72
- await plain.agents.forkCaller({ key: "me2", labels: { lens: "z" } });
73
- const { outcome } = await reviewer.run({ id: "turn-1", prompt: "Review." });
74
- if (isAnswered(outcome)) void outcome.value;
75
- await reviewer.compact({ id: "c-1", prompt: "Keep the findings." });
76
- await reviewer.set({ id: "s-1", effort: "high" });
77
- const batch = await wf.stage("first", () => wf.receive());
78
- void batch;
79
- const turn = await reviewer.start({ id: "turn-2", prompt: "Again." });
80
- const won = await wf.race("review", { done: turn, quiet: "1d" });
81
- if (won.done && turn.is({ name: "agent:ended", from: "r", data: won.done })) void turn.id;
82
- for await (const next of wf.select({ done: turn })) void next.done;
83
- // One context and one agent: the event-only names are the same types.
84
- const context: EventWorkflowContext<Events> = wf;
85
- const agent: EventAgentRef = reviewer;
86
- const spec: EventAgentOpenSpec = { key: "k", runtime: "claude", keepAlive: {} };
87
- const agents: EventAgentDirectory = context.agents;
88
- void [agent satisfies AgentRef, spec satisfies AgentOpenSpec, agents satisfies AgentDirectory];
89
- }
90
-
91
- export async function deprecatedAgents(): Promise<void> {
92
- const worker = await plain.agents.open({ key: "worker", runtime: "codex" });
93
- await worker.fork({ key: "w2", placement: "headless", metered: true, effort: "low" });
94
- await plain.agents.forkCaller({ key: "me2", placement: "headless" });
95
- await plain.agents.stop("worker", "done");
96
- }
97
-
98
- export async function caller(): Promise<void> {
99
- const me = await plain.agents.caller({ key: "me" });
100
- if (!me) return;
101
- // @ts-expect-error The calling session's model and effort are the operator's.
102
- await me.set({ effort: "high" });
103
- // @ts-expect-error The calling session is the operator's, and is not compacted.
104
- await me.compact({ prompt: "Keep the plan." });
105
- // @ts-expect-error A fork of the calling session takes no tools.
106
- await me.fork({ key: "me3", tools: {} });
107
- // @ts-expect-error A fork continues its parent's session on its harness and model.
108
- await me.fork({ key: "me4", runtime: { model: "gpt-6-luna" } });
109
- // @ts-expect-error The caller is not an agent the run opened.
110
- const opened: AgentRef = me;
111
- void opened;
112
- const fork = await me.fork({ key: "me5", runtime: { placement: "headless", metered: true } });
113
- const ref: CallerRef = fork;
114
- await wf.messages.allow({ between: [me, fork], purpose: "Agree on the plan." });
115
- const won = await wf.race("either", { mine: me, theirs: ref, quiet: "1h" });
116
- if (won.mine && me.is({ name: "agent:ended", from: "me", data: won.mine })) void won.mine;
117
- await fork.close("enough");
118
- await me.close();
119
- }
120
-
121
- export async function time(): Promise<void> {
122
- const reviewer = await wf.agents.open({ key: "r", runtime: "claude", timeout: "90s" });
123
- const until = new Date(Date.now() + 60_000);
124
- await reviewer.run({ prompt: "Review.", timeout: "1.5h" });
125
- await reviewer.run({ prompt: "Review.", timeout: until });
126
- await reviewer.run({ prompt: "Review.", timeout: false });
127
- await reviewer.start({ prompt: "Review.", timeout: "10m" });
128
- await reviewer.compact({ prompt: "Keep it.", timeout: "2m" });
129
- await reviewer.set({ effort: "high", timeout: "30s" });
130
- await reviewer.fork({ key: "r2", timeout: "1m" });
131
- await plain.agents.forkCaller({ key: "me6", timeout: until });
132
- await plain.parallel([1], async (n) => n, { timeout: "20m" });
133
- await plain.decisions.decide({
134
- key: "ok",
135
- model: "jev",
136
- state: "x",
137
- questions: { ok: yesNo("Is it ok?") },
138
- timeout: "30s",
139
- });
140
- await wf.agents.open({
141
- key: "k",
142
- runtime: "claude",
143
- keepAlive: { window: "90s" },
144
- tools: { look: tool({ description: "Look.", handler: () => "seen", timeout: "30s" }) },
145
- });
146
- await wf.messages.allow({ from: reviewer, to: reviewer, purpose: "p", timeout: "30m" });
147
- wf.timer("in-90s", "90s");
148
- wf.timer("in-1.5h", "1.5h");
149
- wf.timer("at", until);
150
- await wf.sleep("a-while", "14d");
151
- const every: Duration = duration(String(JSON.parse('"2h"')));
152
- void every;
153
- const end: Date | undefined = plain.deadline;
154
- void end;
155
- // @ts-expect-error A duration is a decimal and s, m, h or d.
156
- await reviewer.run({ prompt: "Review.", timeout: "10x" });
157
- // @ts-expect-error Nor a number: milliseconds are what timeoutMs took.
158
- await reviewer.run({ prompt: "Review.", timeout: 60_000 });
159
- // @ts-expect-error A timer's duration is the same grammar.
160
- wf.timer("typo", "10x");
161
- // @ts-expect-error A route's timeout is a length of time per question, not a time.
162
- await wf.messages.allow({ from: reviewer, to: reviewer, purpose: "p", timeout: until });
163
- // @ts-expect-error A tool's timeout is each call's budget, not a time.
164
- tool({ description: "Look.", handler: () => "seen", timeout: until });
165
- }
166
-
167
- export async function deprecatedTime(): Promise<void> {
168
- const deadline = { unixMilliseconds: Date.now() + 60_000 };
169
- const reviewer = await wf.agents.open({ key: "r", runtime: "claude", deadline });
170
- await reviewer.run({ prompt: "Review.", timeoutMs: 60_000, nudge: { deadline } });
171
- await reviewer.run({ prompt: "Review.", deadline });
172
- await reviewer.compact({ prompt: "Keep it.", timeoutMs: 60_000 });
173
- await reviewer.set({ effort: "high", deadline });
174
- await plain.parallel([1], async (n) => n, { deadline });
175
- await plain.decisions.decide({
176
- key: "ok",
177
- model: "jev",
178
- state: "x",
179
- questions: { ok: yesNo("Is it ok?") },
180
- deadline,
181
- });
182
- await wf.agents.open({
183
- key: "k",
184
- runtime: "claude",
185
- keepAlive: { timeoutMs: 90_000 },
186
- tools: { look: tool({ description: "Look.", handler: () => "seen", timeoutMs: false }) },
187
- });
188
- await wf.messages.allow({ from: reviewer, to: reviewer, purpose: "p", timeoutMs: 60_000 });
189
- const left = plain.deadline ? plain.deadline.unixMilliseconds - Date.now() : undefined;
190
- void left;
191
- }
192
-
193
- export const old = defineExecutableWorkflow({
194
- definition: {
195
- meta: { name: "old", description: "beta.2's shape.", whenToUse: "Never." },
196
- run: async () => null,
197
- },
198
- });
199
-
200
- export async function current(): Promise<void> {
201
- const fixer = await wf.agents.open({ key: "fixer", runtime: "claude" });
202
- await fixer.start({ prompt: "Fix it." });
203
- const first = await wf.race(
204
- "fix-or-quiet",
205
- { fixed: fixer, quiet: "1d" },
206
- { label: () => `fixing at ${wf.now().toISOString()}` },
207
- );
208
- if (first.quiet) await fixer.turn?.cancel("quiet for a day");
209
- if (first.fixed?.outcome.answered) void first.fixed.outcome.value;
210
- await wf.receive({ label: () => "waiting" });
211
- }
212
-
213
- export default defineWorkflow({
214
- meta: { name: "current", description: "The new forms." },
215
- run: async () => null,
216
- });
217
-
218
- // Story 039 task 7: participants and agents routed to the code are declared in `events`.
219
- const Change: OutputSchema<{ id: string }> = {
220
- type: "object",
221
- properties: { id: { type: "string" } },
222
- required: ["id"],
223
- };
224
-
225
- export const declaring = defineWorkflow({
226
- meta: { name: "declaring", description: "Declares what wakes it." },
227
- events: { change: Change, "thread:reply": ParticipantReply, "fixer:ended": AgentEnded },
228
- async run(wf) {
229
- const thread: ParticipantRef = await wf.participants.connect({ key: "thread" });
230
- await thread.post("Looking.");
231
- // @ts-expect-error A ParticipantRef is not a source: race the signal of its declared reply.
232
- await wf.race("bare", { reply: thread });
233
- // @ts-expect-error is(e) stays on timers and agents: a reply is told by its name.
234
- void thread.is;
235
- const fixer = await wf.agents.open({ key: "fixer", runtime: "claude" });
236
- await fixer.start({ prompt: "Fix it." });
237
- for (const e of await wf.receive()) {
238
- if (e.name === "thread:reply") {
239
- const reply: ParticipantReply = e.data;
240
- void reply.author;
241
- }
242
- if (e.name === "fixer:ended") void e.data.outcome.answered;
243
- // @ts-expect-error A misspelled end is no event the workflow declares.
244
- if (e.name === "fxier:ended") void e;
245
- if (fixer.is(e)) void e.data.meta;
246
- // Narrowed under the name it arrives by, the declared one included.
247
- if (fixer.is(e) && e.name === "fixer:ended") void e.data.outcome;
248
- }
249
- const next = await wf.race("approval", { reply: wf.signal("thread:reply"), quiet: "1d" });
250
- // @ts-expect-error An agent's end is waited on by naming the agent: { done: fixer }.
251
- wf.signal("fixer:ended");
252
- if (next.reply) void next.reply.data.text;
253
- // @ts-expect-error A run speaks for no participant: wf.emit sends its target's own events.
254
- await wf.emit(declaringTarget, "k", "thread:reply", { text: "hi" });
255
- return null;
256
- },
257
- });
258
- declare const declaringTarget: typeof declaring;
259
-
260
- // @ts-expect-error `{key}:reply` takes ParticipantReply, and no other schema.
261
- export const wrongSchema = defineWorkflow({
262
- meta: { name: "wrong", description: "An awf name with a schema of its own." },
263
- events: { "thread:reply": Change },
264
- run: async () => null,
265
- });
266
-
267
- // @ts-expect-error Only `{key}:reply` and `{key}:ended` have a colon.
268
- export const unknownColon = defineWorkflow({
269
- meta: { name: "colon", description: "A colon name awf doesn't make." },
270
- events: { "thread:said": ParticipantReply },
271
- run: async () => null,
272
- });
273
-
274
- export const awfStart = defineWorkflow({
275
- meta: { name: "start", description: "A reply can't start a run." },
276
- file: "/flows/start.ts",
277
- events: { change: Change, "thread:reply": ParticipantReply },
278
- // @ts-expect-error `starts` takes only the workflow's own events.
279
- starts: ["thread:reply"],
280
- run: async () => null,
281
- });
282
-
283
- export const stillCompiles = defineWorkflow({
284
- meta: { name: "old-routing", description: "The deprecated form." },
285
- events: { change: Change },
286
- async run(wf) {
287
- const thread: CodeParticipantRef = await wf.participants.connect({ key: "thread", to: "code" });
288
- await thread.post("Looking.");
289
- for (const e of await wf.receive()) if (thread.is(e)) void e.data.text;
290
- const next = await wf.race("reply", { reply: thread, quiet: "1d" });
291
- if (next.reply) void next.reply.text;
292
- // A spec held in a variable, which no excess-property check sets apart, still routes.
293
- const spec: CodeParticipantSpec = { key: "pager", to: "code" };
294
- const pager = await wf.participants.connect(spec);
295
- for (const e of await wf.receive()) if (pager.is(e)) void e.data.text;
296
- await wf.race("page", { reply: pager, quiet: "1d" });
297
- return null;
298
- },
299
- });
300
-
301
- // As before task 7: an end read from a loosely typed event narrows to `AgentEndedEvent`.
302
- declare const loose: WorkflowEvent<string, JsonValue>;
303
- declare function handleEnd(e: AgentEndedEvent<{ n: number }>): void;
304
- declare function handleReply(e: ParticipantReplyEvent): void;
305
- export async function looseEvents(inv: AgentRef, thread: CodeParticipantRef): Promise<void> {
306
- if (inv.is<{ n: number }>(loose)) handleEnd(loose);
307
- if (inv.is(loose)) void loose.data.outcome;
308
- if (thread.is(loose)) handleReply(loose);
309
- }