@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.
@@ -0,0 +1,87 @@
1
+ export type SandboxKey = string;
2
+
3
+ /**
4
+ * A host name: exact (`registry.npmjs.org`), or `*.` and a name for its subdomains only
5
+ * (`*.npmjs.org`, which does not match `npmjs.org`). No scheme, port, path or IP address.
6
+ */
7
+ export type Domain = string;
8
+
9
+ /** What the agents inside see and reach. Every provider enforces it; each does so its own way. */
10
+ export type SandboxReach = {
11
+ /** Host paths visible at the same path, read-only, beyond the sandbox's working directory. */
12
+ read?: readonly string[];
13
+ /** Host paths visible at the same path and writable. `"."` makes the working directory writable. */
14
+ write?: readonly string[];
15
+ /** Domains reachable beyond each harness's model API. */
16
+ network?: readonly Domain[];
17
+ /**
18
+ * Host unix sockets the agents inside may connect to: a socket, or a directory whose sockets they
19
+ * may connect to and, when it is also in `write`, create. Nothing else of the host's is reachable
20
+ * this way. srt only: docker refuses a sandbox that names one.
21
+ */
22
+ sockets?: readonly string[];
23
+ /**
24
+ * Keeps each harness's model-side web tools, which awf otherwise turns off inside: a search runs
25
+ * on the model provider's servers, past any `network` rule, so it can carry what the agents read
26
+ * out and bring the web in (X15).
27
+ */
28
+ webSearch?: boolean;
29
+ };
30
+
31
+ /** Anthropic's sandbox-runtime on this machine: the host system and the operator's toolchain. */
32
+ export type SrtEnvironment = Record<string, never>;
33
+
34
+ /** A container from `image`, running as the operator's uid. Absent image: the operator's default. */
35
+ export type DockerEnvironment = { image?: string };
36
+
37
+ /** The environments contract names, each the key of its settings in a spec and of its provider. */
38
+ export const SANDBOX_ENVIRONMENTS = ["srt", "docker"] as const;
39
+ export type SandboxEnvironmentKey = (typeof SANDBOX_ENVIRONMENTS)[number];
40
+
41
+ /** At most one environment. Naming one pins the provider; naming none uses the operator's default. */
42
+ export type SandboxEnvironment =
43
+ | { srt: SrtEnvironment; docker?: never }
44
+ | { docker: DockerEnvironment; srt?: never }
45
+ | { srt?: never; docker?: never };
46
+
47
+ export type SandboxSpec = SandboxReach & SandboxEnvironment;
48
+
49
+ /**
50
+ * A git directory the sandbox's `cwd` or a reach path belongs to, found when it opens: a
51
+ * repository's `.git`, a worktree's own gitdir and its common one, or a repository's nested in a
52
+ * writable path. Readable inside. Writable when the worktree it serves is, so a writable worktree
53
+ * can commit under every provider; what the host's git runs or follows in it never is.
54
+ */
55
+ export type Gitdir = {
56
+ path: string;
57
+ writable: boolean;
58
+ /**
59
+ * Writable only through a linked worktree: the main worktree's `index` and `HEAD` in it stay
60
+ * read-only, so an agent cannot stage what the operator commits next.
61
+ */
62
+ linked?: true;
63
+ };
64
+
65
+ export type SandboxOpenSpec = SandboxSpec & {
66
+ key: SandboxKey;
67
+ /** Defaults to the workflow's working directory. Always readable inside. */
68
+ cwd?: string;
69
+ };
70
+
71
+ declare const sandboxRef: unique symbol;
72
+
73
+ /** A sandbox this run opened. Only the engine makes one; a look-alike object is not a ref. */
74
+ export interface SandboxRef {
75
+ readonly [sandboxRef]: true;
76
+ readonly key: SandboxKey;
77
+ /** Grows as providers are added: never switch over it exhaustively. */
78
+ readonly provider: SandboxEnvironmentKey;
79
+ }
80
+
81
+ /** A spec given inline to one agent: a private sandbox. It cannot carry a ref's or an open spec's fields. */
82
+ export type InlineSandboxSpec = SandboxSpec & { key?: never; cwd?: never; provider?: never };
83
+
84
+ export interface SandboxDirectory {
85
+ /** Opens a sandbox for this run. It closes when the run does, after every agent in it. */
86
+ open(spec: SandboxOpenSpec): Promise<SandboxRef>;
87
+ }
@@ -0,0 +1,64 @@
1
+ import type {
2
+ AgentOpenSpec,
3
+ InlineSandboxSpec,
4
+ SandboxEnvironment,
5
+ SandboxEnvironmentKey,
6
+ SandboxOpenSpec,
7
+ SandboxRef,
8
+ SandboxSpec,
9
+ WorkflowContext,
10
+ } from "./index";
11
+
12
+ declare const context: WorkflowContext;
13
+ declare const ref: SandboxRef;
14
+
15
+ const headless = { placement: "headless" } as const;
16
+
17
+ async function acceptedShapes(): Promise<void> {
18
+ const box = await context.sandboxes.open({
19
+ key: "build",
20
+ write: ["."],
21
+ network: ["registry.npmjs.org", "*.npmjs.org"],
22
+ docker: { image: "awf-agent:node22" },
23
+ });
24
+ const local = await context.sandboxes.open({
25
+ key: "lint",
26
+ cwd: "/repo",
27
+ sockets: ["/tmp/app"],
28
+ webSearch: true,
29
+ srt: {},
30
+ });
31
+ const unpinned = { read: ["~/notes"] } satisfies SandboxSpec;
32
+ const opened: AgentOpenSpec[] = [
33
+ { key: "coder", runtime: { alias: "codex", ...headless }, sandbox: box },
34
+ { key: "linter", runtime: "pi", sandbox: local },
35
+ { key: "reviewer", runtime: "codex", sandbox: {} },
36
+ { key: "reader", runtime: "codex", sandbox: unpinned },
37
+ { key: "tester", runtime: "claude", sandbox: { write: ["."], docker: {} } },
38
+ ];
39
+ void [box.provider, opened];
40
+ }
41
+
42
+ function rejectedShapes(): void {
43
+ // @ts-expect-error A sandbox has at most one environment.
44
+ const twoEnvironments: SandboxOpenSpec = { key: "both", srt: {}, docker: {} };
45
+ // @ts-expect-error srt takes no settings yet.
46
+ const srtSettings: SandboxSpec = { srt: { image: "node" } };
47
+ const lookAlike = { key: "build", provider: "srt" };
48
+ // @ts-expect-error Only the engine makes a ref; a look-alike is not one.
49
+ const forged: AgentOpenSpec = { key: "coder", runtime: "codex", sandbox: lookAlike };
50
+ // @ts-expect-error An open spec is not an inline one: its key would name a shared sandbox.
51
+ const keyed: AgentOpenSpec = { key: "coder", runtime: "codex", sandbox: { key: "build" } };
52
+ // @ts-expect-error An inline sandbox's working directory is the agent's.
53
+ const inlineCwd: InlineSandboxSpec = { cwd: "/elsewhere" };
54
+ // @ts-expect-error A ref's provider cannot be written into a spec.
55
+ const inlineProvider: InlineSandboxSpec = { provider: "docker" };
56
+ void [twoEnvironments, srtSettings, forged, keyed, inlineCwd, inlineProvider, ref];
57
+ }
58
+
59
+ // Every environment key is a settings field in a spec, and every settings field is a key.
60
+ type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false;
61
+ type SettingsFields = SandboxEnvironment extends infer E ? (E extends E ? keyof E : never) : never;
62
+ const environmentsMatch: Same<SettingsFields, SandboxEnvironmentKey> = true;
63
+
64
+ void [acceptedShapes, rejectedShapes, environmentsMatch];
@@ -0,0 +1,14 @@
1
+ /** An absolute wall-clock deadline. `unixMilliseconds` is a non-negative finite integer. */
2
+ export type AbsoluteDeadline = {
3
+ unixMilliseconds: number;
4
+ };
5
+
6
+ /** The single machine-readable rejection used by workflow waits without terminal outcomes. */
7
+ export class DeadlineExceededError extends Error {
8
+ readonly code = "deadline-exceeded" as const;
9
+
10
+ constructor(readonly deadline: AbsoluteDeadline) {
11
+ super(`Deadline exceeded at ${deadline.unixMilliseconds}`);
12
+ this.name = "DeadlineExceededError";
13
+ }
14
+ }
@@ -0,0 +1,128 @@
1
+ import type { AgentDirectory, OperationRecord } from "./agents";
2
+ import type { WorkflowCallSpec } from "./composition";
3
+ import type { DecisionDirectory } from "./decisions";
4
+ import type { JsonObject, JsonValue, OutputSchema } from "./json";
5
+ import type { Messaging } from "./messaging";
6
+ import type { ParticipantDirectory } from "./participants";
7
+ import type { SandboxDirectory } from "./sandboxes";
8
+ import type { AbsoluteDeadline } from "./timing";
9
+
10
+ export type WorkflowRunId = string;
11
+ export type StepId = string;
12
+ export type SignalId = string;
13
+
14
+ export interface WorkflowMeta {
15
+ /** What its runs are kept under; a file may move or be copied and stay the same workflow. */
16
+ name: string;
17
+ description: string;
18
+ whenToUse?: string;
19
+ /** Semver, recorded with each attempt. */
20
+ version?: string;
21
+ }
22
+
23
+ export interface StepSpec {
24
+ /** Idempotency key scoped to this workflow context. Reusing it with a different spec rejects. */
25
+ id: StepId;
26
+ label?: string;
27
+ }
28
+
29
+ export interface Steps {
30
+ run<T extends JsonValue>(
31
+ spec: StepSpec & { deadline?: AbsoluteDeadline },
32
+ operation: () => Promise<T>,
33
+ ): Promise<T>;
34
+ /** Its requested duration is already a bound, so sleep has no second deadline. */
35
+ sleep(spec: StepSpec & { milliseconds: number }): Promise<void>;
36
+ }
37
+
38
+ export interface ParallelOptions {
39
+ /** Defaults to the current workflow scope deadline. */
40
+ deadline?: AbsoluteDeadline;
41
+ /** Positive integer local maximum; global admission may reduce it. */
42
+ concurrency?: number;
43
+ label?: string;
44
+ }
45
+
46
+ export interface SignalSpec {
47
+ /** Idempotency key scoped to this workflow context. Reusing it with a different spec rejects. */
48
+ id: SignalId;
49
+ name: string;
50
+ /** Defaults to the current workflow scope deadline. */
51
+ deadline?: AbsoluteDeadline;
52
+ }
53
+
54
+ export interface Signals {
55
+ /** Waits for one value and suspends only this branch, never workflow admission. */
56
+ receive(spec: SignalSpec): Promise<string>;
57
+ receive<T extends JsonValue>(spec: SignalSpec, schema: OutputSchema<T>): Promise<T>;
58
+ }
59
+
60
+ /**
61
+ * A stage's end: `stopped` is `workflow.stop` inside it; `failed` covers a throw, a value its
62
+ * schema rejects, and a cancellation.
63
+ */
64
+ export type StageOutcome = "succeeded" | "stopped" | "failed";
65
+
66
+ export interface StageOptions<T extends JsonValue> {
67
+ /** Checks the value when the stage records it, and whenever a continue reuses it. */
68
+ result: OutputSchema<T>;
69
+ /** One line about the value, for the view and the record. */
70
+ summary?: (value: T) => string;
71
+ }
72
+
73
+ export interface WorkflowContext {
74
+ /** The run's id, the same in every attempt of it. */
75
+ readonly runId: WorkflowRunId;
76
+ /** Which `awf run` of the run this is: 1, then one more for each continue. */
77
+ readonly attempt: number;
78
+ /** Working directory and hard bound for this invocation, supplied and enforced by the engine. */
79
+ readonly cwd: string;
80
+ readonly deadline: AbsoluteDeadline;
81
+ readonly agents: AgentDirectory;
82
+ readonly sandboxes: SandboxDirectory;
83
+ /** Closed questions to a decision model: probabilities back in a few hundred milliseconds. */
84
+ readonly decisions: DecisionDirectory;
85
+ readonly participants: ParticipantDirectory;
86
+ readonly messages: Messaging;
87
+ readonly steps: Steps;
88
+ readonly signals: Signals;
89
+ parallel<Item, Result>(
90
+ items: readonly Item[],
91
+ operation: (item: Item, index: number) => Promise<Result>,
92
+ options?: ParallelOptions,
93
+ ): Promise<Result[]>;
94
+
95
+ /**
96
+ * A named step of the run, recorded when it ends. One stage is open at a time, and a name is
97
+ * entered once per attempt; parallel work goes inside a stage. A stage with `result` returns a
98
+ * value it accepts; one without returns nothing.
99
+ */
100
+ stage(name: string, work: () => Promise<void>): Promise<void>;
101
+ stage<T extends JsonValue>(
102
+ name: string,
103
+ options: StageOptions<T>,
104
+ work: () => Promise<T>,
105
+ ): Promise<T>;
106
+
107
+ /**
108
+ * Ends the attempt `stopped`, apart from failed: in the stage it is called from, which a continue
109
+ * then redoes, or between stages, where a continue checks again. Caught, it fails the stage that
110
+ * caught it, or the attempt when the workflow returns. `return workflow.stop(…)` narrows where a
111
+ * bare call doesn't.
112
+ */
113
+ stop(reason: string): never;
114
+
115
+ call<Args extends JsonValue, Result extends JsonValue>(
116
+ spec: WorkflowCallSpec<Args, Result>,
117
+ ): Promise<Result>;
118
+
119
+ /** Completed operations in this workflow scope and its descendants, ordered by dispatch. */
120
+ usage(): OperationRecord[];
121
+
122
+ log(message: string, fields?: JsonObject): void;
123
+ }
124
+
125
+ export interface WorkflowDefinition<Args extends JsonValue, Result extends JsonValue> {
126
+ readonly meta: WorkflowMeta;
127
+ run(context: WorkflowContext, args: Args): Promise<Result>;
128
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.