agent-work-layer 0.1.1 → 0.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0 — 2026-10-09
4
+
5
+ ### Breaking
6
+
7
+ - The MCP server moved to a new package, `agent-work-layer-mcp`, so installing `agent-work-layer` no longer downloads the MCP SDK and zod (7 packages instead of 97).
8
+ - `npx -y agent-work-layer desk` → `npx -y agent-work-layer-mcp` (same flags and environment variables).
9
+ - `import { createDeskServer } from "agent-work-layer/mcp"` → `from "agent-work-layer-mcp"`.
10
+ - `awl desk` now exits with code 2 and prints the new command.
11
+
12
+ ### Added
13
+
14
+ - Spec v0.2: optional `handoff` on Work Orders and Work Reports: decisions made, approaches tried, open questions and next step, so a job can move to another worker without starting over (SPEC.md §4.4). The MCP tools accept it too.
15
+ - `agent-work-layer/validate`: types, schemas and every check, with no Node built-ins. Runs in browsers, Deno and Bun.
16
+ - `ORDER_SPECS` and `REPORT_SPECS`: every spec version this package reads.
17
+ - `awl new order` and `awl new report` print starter documents that validate together as printed (#7, thanks @brunnojob).
18
+ - SPEC.md §13, Related work: Agent Contracts, A2A tasks, the IETF WIMSE cross-org delegation draft and KYA-OS / MCP-I.
19
+
20
+ ### Changed
21
+
22
+ - The repository is now an npm workspace: the core is in `packages/core`, the MCP server in `packages/mcp`. Published package contents are unchanged by this.
23
+
24
+ - The desk writes `awl/work-order@0.2` and `awl/work-report@0.2`. Documents marked `@0.1` are still accepted, so stored orders keep working.
25
+ - Schemas and the version number are built into the code (`packages/core/src/generated.ts`) instead of read from disk at startup.
26
+
3
27
  ## 0.1.1 — 2026-10-09
4
28
 
5
29
  - Package metadata now links to the GitHub repository, homepage and issue tracker.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # agent-work-layer
2
2
 
3
- **Work Orders and Work Reports for AI agents.** A typed SDK, validator, lifecycle engine and MCP server for handing work from one agent to another, with checkable results.
3
+ **Work Orders and Work Reports for AI agents.** A typed SDK, validator and lifecycle engine for handing work from one agent to another, with checkable results. The MCP server is a separate package, [`agent-work-layer-mcp`](packages/mcp/).
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/agent-work-layer.svg)](https://www.npmjs.com/package/agent-work-layer)
6
6
  [![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
@@ -25,9 +25,13 @@ More diagrams: [message sequence](docs/sequence.svg) · [lifecycle](docs/lifecyc
25
25
 
26
26
  ## Install
27
27
 
28
- ```sh
29
- npm install agent-work-layer
30
- ```
28
+ Install only the part you use:
29
+
30
+ | You want to… | Install | Packages |
31
+ |---|---|---|
32
+ | Validate orders and reports, or run a desk in your code | `npm install agent-work-layer` | 7 |
33
+ | Validate in a browser, Deno or Bun | `npm install agent-work-layer`, then import `agent-work-layer/validate` | 7 |
34
+ | Connect agent sessions (e.g. Claude Code) through MCP | `npx -y agent-work-layer-mcp` ([setup](#run-as-an-mcp-server)) | ~100 |
31
35
 
32
36
  Node.js 20.11 or later. ESM only. Types included.
33
37
 
@@ -84,6 +88,28 @@ The desk enforces the spec as you go:
84
88
  - Cost within budget and finishing before the deadline. Going over either expires the order.
85
89
  - Sub-orders can only narrow their parent's authority.
86
90
 
91
+ ### Hand off unfinished work
92
+
93
+ When a worker fails, gets blocked or is cancelled, it can say what the next worker should know. The manager passes that along in the next order:
94
+
95
+ ```ts
96
+ await desk.submitReport(order.id, {
97
+ status: "failed",
98
+ error: "Export runs out of memory on files over 2 GB",
99
+ handoff: {
100
+ decisions: [{ text: "Stream rows instead of buffering", why: "Memory limit" }],
101
+ tried: [{ text: "xlsx library", result: "Too slow on large files" }],
102
+ next_step: "Add tests for null cells",
103
+ },
104
+ /* result, evidence, cost, tools_used */
105
+ }, "agent:researcher");
106
+
107
+ const { handoff } = (await desk.get(order.id)).reports.at(-1)!;
108
+ await desk.create({ ...nextJob, to: "agent:other-worker", handoff }, "agent:manager");
109
+ ```
110
+
111
+ `context` lists what to read; `handoff` says what was decided and already tried. See [SPEC.md §4.4](SPEC.md#44-handoff).
112
+
87
113
  ## Validate documents
88
114
 
89
115
  ```ts
@@ -98,23 +124,27 @@ Every finding carries the rule id from [SPEC.md §7](SPEC.md#7-checks). `invalid
98
124
 
99
125
  The JSON Schemas are exported too: `workOrderSchema`, `workReportSchema`, or `agent-work-layer/schemas/work-order.schema.json`.
100
126
 
127
+ `agent-work-layer/validate` has the same checks, types and schemas without the desk. It imports nothing from Node, so it runs in browsers, Deno and Bun. (Cloudflare Workers are not supported: the validator compiles schemas to code at runtime, which Workers block.)
128
+
101
129
  ## Run as an MCP server
102
130
 
131
+ The server lives in its own package, [`agent-work-layer-mcp`](packages/mcp/), so people who only validate documents don't download the MCP SDK.
132
+
103
133
  Each agent session runs its own desk process. Sessions share one folder, and each is locked to one identity, so a worker cannot approve its own work.
104
134
 
105
135
  ```sh
106
136
  # Claude Code: a manager session and a worker session, each added in its own project folder
107
- claude mcp add awl -e AWL_STORE="$HOME/.awl" -e AWL_ACTOR=agent:manager -- npx -y agent-work-layer desk
108
- claude mcp add awl -e AWL_STORE="$HOME/.awl" -e AWL_ACTOR=agent:worker -- npx -y agent-work-layer desk
137
+ claude mcp add awl -e AWL_STORE="$HOME/.awl" -e AWL_ACTOR=agent:manager -- npx -y agent-work-layer-mcp
138
+ claude mcp add awl -e AWL_STORE="$HOME/.awl" -e AWL_ACTOR=agent:worker -- npx -y agent-work-layer-mcp
109
139
  ```
110
140
 
111
141
  Tools: `create_work_order`, `list_work_orders`, `get_work_order`, `accept_work_order`, `decline_work_order`, `start_work_order`, `submit_work_report`, `answer_question`, `review_work_report`, `cancel_work_order`, `wait_for_update`.
112
142
 
113
- To embed the server in your own process or transport:
143
+ To embed the server in your own process or transport (`npm install agent-work-layer agent-work-layer-mcp`):
114
144
 
115
145
  ```ts
116
146
  import { WorkDesk, FileStore } from "agent-work-layer";
117
- import { createDeskServer } from "agent-work-layer/mcp";
147
+ import { createDeskServer } from "agent-work-layer-mcp";
118
148
 
119
149
  const server = createDeskServer({ desk: new WorkDesk({ store: new FileStore("./.awl") }), actor: "agent:manager" });
120
150
  await server.connect(transport);
@@ -123,13 +153,21 @@ await server.connect(transport);
123
153
  ## CLI
124
154
 
125
155
  ```sh
156
+ npx agent-work-layer new order > order.json
157
+ npx agent-work-layer new report > report.json
126
158
  npx agent-work-layer validate order.json report.json # exit 1 on invalid, prints rule ids
127
159
  npx agent-work-layer validate child.json --parent order.json
128
160
  npx agent-work-layer validate order.json report.json --json
129
161
  npx agent-work-layer schema order
130
- npx agent-work-layer desk --store ./.awl --actor agent:worker
162
+ npx -y agent-work-layer-mcp --store ./.awl --actor agent:worker # the MCP desk (was "awl desk" before 0.2)
131
163
  ```
132
164
 
165
+ `new order` starts with one criterion, a USD 1 budget, no allowed tools and a
166
+ deadline one hour after creation. `new report` starts as `blocked`, with a question
167
+ and no evidence of completed work. Both templates validate together as printed.
168
+ Replace the placeholder goal, identities, criterion and report contents before
169
+ using them; keep the report's `order_id` equal to the order's `id`.
170
+
133
171
  ## Lifecycle
134
172
 
135
173
  ```
@@ -160,7 +198,8 @@ Any active state ──cancel──▶ cancelled · deadline or budget excee
160
198
  | `toolAllowed`, `patternCovered` | Tool pattern matching |
161
199
  | `nextState`, `allowedActions`, `STATES`, `ROLE_FOR`, `isTerminal` | The state machine |
162
200
  | `AwlError`, `isAwlError` | Typed errors with `code` and `findings` |
163
- | `createDeskServer` (from `agent-work-layer/mcp`) | MCP server for a desk |
201
+ | `ORDER_SPEC`, `REPORT_SPEC`, `ORDER_SPECS`, `REPORT_SPECS` | The spec version written, and every version read |
202
+ | `createDeskServer` (from `agent-work-layer-mcp`) | MCP server for a desk |
164
203
 
165
204
  `WorkDesk` options: `store`, `clock` (for tests), `newId`, `onEvent(event, record)` (webhooks, logging, metrics).
166
205
 
@@ -187,13 +226,16 @@ The desk trusts the `actor` it is given. In production, derive the actor from au
187
226
 
188
227
  ## Development
189
228
 
229
+ The repo is an npm workspace with two packages: the core in [`packages/core`](packages/core/) and the MCP server in [`packages/mcp`](packages/mcp/). The spec, schemas and examples stay at the root; the core copies them in when it is packed.
230
+
190
231
  ```sh
191
232
  npm install
192
- npm test # unit, integration and end-to-end tests
233
+ npm test # core and MCP tests (builds the core first)
193
234
  npm run test:coverage
194
235
  npm run typecheck
195
- npm run test:package # builds, packs, installs the tarball in a clean project and uses it
236
+ npm run test:package # packs both packages, installs them in clean projects and uses them
196
237
  npm run check # all of the above; runs automatically before npm publish
238
+ npm run generate # after editing schemas/*.json: refreshes packages/core/src/generated.ts
197
239
  ```
198
240
 
199
241
  ## Contributing
package/SPEC.md CHANGED
@@ -1,6 +1,6 @@
1
- # Agent Work Layer (AWL) — Specification v0.1 (draft)
1
+ # Agent Work Layer (AWL) — Specification v0.2 (draft)
2
2
 
3
- Status: draft · Date: 2026-10-09
3
+ Status: draft · Date: 2026-10-09 · Changes from v0.1: the optional `handoff` field (§4.4); readers accept both versions (§11); related work (§13).
4
4
 
5
5
  ## 1. Purpose
6
6
 
@@ -39,7 +39,7 @@ The key words MUST, SHOULD and MAY are used as in RFC 2119.
39
39
 
40
40
  | Field | Req | Type | Meaning |
41
41
  |----------------|-----|---------------------|---------|
42
- | `spec` | yes | `"awl/work-order@0.1"` | Format and version. |
42
+ | `spec` | yes | `"awl/work-order@0.2"` | Format and version. Readers also accept `@0.1` (§11). |
43
43
  | `id` | yes | string | Unique job id, set by the desk or manager. |
44
44
  | `created_at` | yes | timestamp | When the order was issued. |
45
45
  | `from` | yes | agent ref | The manager. |
@@ -52,6 +52,7 @@ The key words MUST, SHOULD and MAY are used as in RFC 2119.
52
52
  | `escalate_to` | no | agent ref | Who to ask when blocked. Default: `from`. |
53
53
  | `inputs` | no | object | Structured inputs for the job. |
54
54
  | `context` | no | array of `{uri, note?}` | Pointers to material the worker should read. |
55
+ | `handoff` | no | Handoff (§4.4) | What earlier work on this job established. |
55
56
  | `output_schema`| no | JSON Schema | Shape that `result.output` MUST match when done. |
56
57
  | `parent_id` | no | string | Set when this order was split from a larger one (§8). |
57
58
  | `metadata` | no | object | Free-form; ignored by checks. |
@@ -77,11 +78,35 @@ A Budget is an object with at least one of these units. Cost (in the report) use
77
78
  A tool is named `<server>/<tool>`, where `<server>` is the MCP server name as the worker knows it.
78
79
  Patterns: `server/tool` (exactly one tool), `server/*` (every tool on that server), `*` (any tool).
79
80
 
81
+ ### 4.4 Handoff
82
+
83
+ When a job moves from one worker to another (after a failure, a cancellation or a block), the next worker should not start from nothing. `context` says what to read; `handoff` says what was decided, what was already tried, and where things stand.
84
+
85
+ ```json
86
+ "handoff": {
87
+ "summary": "Parser done; export fails on files over 2 GB",
88
+ "decisions": [{ "text": "Stream rows instead of buffering", "why": "Memory limit" }],
89
+ "tried": [{ "text": "xlsx library", "result": "Too slow on large files" }],
90
+ "open_questions": ["Quote style for commas?"],
91
+ "next_step": "Add tests for null cells"
92
+ }
93
+ ```
94
+
95
+ | Field | Type | Meaning |
96
+ |------------------|-------------------------------|---------|
97
+ | `summary` | string | Where the work stands. |
98
+ | `decisions` | array of `{text, why?}` | Choices already made, and why. The next worker SHOULD keep them unless it has a reason not to. |
99
+ | `tried` | array of `{text, result?}` | Approaches already attempted and what happened, so they are not repeated. |
100
+ | `open_questions` | array of string | Questions still unanswered. |
101
+ | `next_step` | string | The most useful thing to do next. |
102
+
103
+ Every field is optional, but a handoff MUST NOT be empty. A Work Report MAY carry a handoff (§5); a worker SHOULD include one when it reports `failed` or `blocked`. A manager reissuing the job MAY copy it, edited or not, into the new order. Handoff content is the previous worker's account, not verified fact; it is not checked against evidence.
104
+
80
105
  ## 5. Work Report
81
106
 
82
107
  | Field | Req | Type | Meaning |
83
108
  |---------------|-----|-------------------------|---------|
84
- | `spec` | yes | `"awl/work-report@0.1"` | Format and version. |
109
+ | `spec` | yes | `"awl/work-report@0.2"` | Format and version. Readers also accept `@0.1` (§11). |
85
110
  | `order_id` | yes | string | The order this answers. |
86
111
  | `from` | yes | agent ref | The worker. |
87
112
  | `status` | yes | `done` \| `blocked` \| `failed` | Outcome of this attempt. |
@@ -94,6 +119,7 @@ Patterns: `server/tool` (exactly one tool), `server/*` (every tool on that serve
94
119
  | `attempt` | no | integer ≥ 1 | 1 for the first report, +1 after each rework. |
95
120
  | `questions` | no | array of string | Open questions. Required when `blocked`. |
96
121
  | `error` | no | string | What went wrong. Required when `failed`. |
122
+ | `handoff` | no | Handoff (§4.4) | What whoever continues this job should know. |
97
123
  | `metadata` | no | object | Free-form. |
98
124
 
99
125
  ### 5.1 Evidence
@@ -184,7 +210,7 @@ A desk MUST reject a child order that breaks C1–C3. The parent's reported cost
184
210
  How AWL documents travel. A desk MAY support several.
185
211
 
186
212
  - **Files**: `<id>.order.json` and `<id>.report.json` side by side. Good for tests and audit archives.
187
- - **MCP**: a desk exposed as an MCP server with tools such as `create_work_order`, `submit_work_report` and `review_work_report`. The reference implementation in this repo works this way.
213
+ - **MCP**: a desk exposed as an MCP server with tools such as `create_work_order`, `submit_work_report` and `review_work_report`. The reference implementation is the `agent-work-layer-mcp` package in this repo.
188
214
  - **A2A**: the document travels as a data part of an A2A message with the AWL media type. The A2A task id SHOULD be stored in `metadata.a2a_task_id`.
189
215
 
190
216
  ## 10. Security
@@ -197,8 +223,21 @@ How AWL documents travel. A desk MAY support several.
197
223
 
198
224
  `spec` carries the version. Minor versions (0.x) may add optional fields only. Readers MUST reject a major version they do not know.
199
225
 
226
+ A v0.2 reader MUST accept documents marked `@0.1` and `@0.2`, and MUST reject any other version. A v0.2 writer marks new documents `@0.2`. Every v0.1 document is a valid v0.2 document once its version is accepted.
227
+
200
228
  ## 12. Planned for later versions
201
229
 
202
230
  - Delegation Pass: a signed, standalone grant of budget, tools and time.
203
231
  - Track record: an aggregate of approved reports per worker.
204
232
  - Streaming progress events while `working`.
233
+
234
+ ## 13. Related work
235
+
236
+ AWL overlaps with these efforts and is meant to work alongside them.
237
+
238
+ | Work | What it covers | How AWL relates |
239
+ |------|----------------|-----------------|
240
+ | Agent Contracts (Ye and Tan, 2026, [arXiv:2601.08815](https://arxiv.org/abs/2601.08815)) | A formal model of resource-bounded agents: budgets, deadlines, weighted success criteria, and sub-contracts whose budgets may not exceed the parent's. | Close in intent. AWL is a wire format (JSON documents, JSON Schemas, numbered checks) with evidence per criterion, separate manager and worker roles, and review. Its sub-order rules (§8) are the same conservation idea. |
241
+ | A2A tasks ([a2a-protocol.org](https://a2a-protocol.org)) | Task lifecycle and message transport between agents. | AWL describes the job and its proof; the A2A binding (§9) carries AWL documents inside A2A messages. |
242
+ | IETF [draft-reece-wimse-cross-org-delegation](https://datatracker.ietf.org/doc/draft-reece-wimse-cross-org-delegation/) | Requirements for authority that can only narrow at each delegation hop, verifiable across organizations. | Same principle as §8. Input for the planned Delegation Pass (§12). |
243
+ | KYA-OS / MCP-I ([@kya-os/mcp](https://www.npmjs.com/package/@kya-os/mcp)) | Agent identity, delegation credentials and verifiable audit for MCP servers. | Identifies the actor, not the job. A desk can use such credentials to meet §10's requirement to authenticate `actor`. |
package/dist/cli.js CHANGED
@@ -1,21 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync } from "node:fs";
3
3
  import { parseArgs } from "node:util";
4
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import { WorkDesk } from "./desk.js";
6
- import { createDeskServer } from "./mcp.js";
7
- import { FileStore } from "./store.js";
8
4
  import { hasInvalid, validateChildOrder, validateOrder, validateReport, workOrderSchema, workReportSchema } from "./validate.js";
5
+ import { ORDER_SPEC, REPORT_SPEC } from "./types.js";
9
6
  import { VERSION } from "./version.js";
10
7
  const HELP = `awl ${VERSION} — Agent Work Layer
11
8
 
12
9
  Usage:
13
10
  awl validate <order.json> [report.json] [--parent <parent-order.json>] [--json]
14
- awl desk [--store <dir>] [--actor <agent-ref>] Run the desk as an MCP server on stdio
15
11
  awl schema <order|report> Print a JSON Schema
12
+ awl new <order|report> Print a starter document
16
13
  awl --version | --help
17
14
 
18
- Environment for "awl desk": AWL_STORE (default ./.awl), AWL_ACTOR.
15
+ The MCP desk server is a separate package: npx -y agent-work-layer-mcp
19
16
  Exit codes: 0 ok, 1 invalid document, 2 usage or file error.`;
20
17
  function readJson(path) {
21
18
  try {
@@ -27,6 +24,38 @@ function readJson(path) {
27
24
  }
28
25
  class UsageError extends Error {
29
26
  }
27
+ function newDocument(args) {
28
+ const [which] = args;
29
+ if (args.length !== 1 || (which !== "order" && which !== "report")) {
30
+ throw new UsageError('new needs "order" or "report"');
31
+ }
32
+ const now = new Date();
33
+ const document = which === "order" ? {
34
+ spec: ORDER_SPEC,
35
+ id: "wo_example",
36
+ created_at: now.toISOString(),
37
+ from: "agent:manager",
38
+ to: "agent:worker",
39
+ goal: "Replace with the work to be done",
40
+ criteria: [{ id: "c1", text: "Replace with a checkable acceptance criterion" }],
41
+ budget: { usd: 1 },
42
+ deadline: new Date(now.getTime() + 3_600_000).toISOString(),
43
+ tools: [],
44
+ } : {
45
+ spec: REPORT_SPEC,
46
+ order_id: "wo_example",
47
+ from: "agent:worker",
48
+ status: "blocked",
49
+ result: { summary: "Replace with the work performed so far" },
50
+ evidence: [],
51
+ cost: { usd: 0 },
52
+ finished_at: now.toISOString(),
53
+ tools_used: [],
54
+ questions: ["What work should be completed for criterion c1?"],
55
+ };
56
+ console.log(JSON.stringify(document, null, 2));
57
+ return 0;
58
+ }
30
59
  function validate(args) {
31
60
  const { values, positionals } = parseArgs({ args, allowPositionals: true, options: { parent: { type: "string" }, json: { type: "boolean" } } });
32
61
  const [orderPath, reportPath] = positionals;
@@ -58,20 +87,15 @@ function validate(args) {
58
87
  }
59
88
  return failed ? 1 : 0;
60
89
  }
61
- async function desk(args) {
62
- const { values } = parseArgs({ args, options: { store: { type: "string" }, actor: { type: "string" } } });
63
- const store = new FileStore(values.store ?? process.env.AWL_STORE ?? ".awl");
64
- const server = createDeskServer({ desk: new WorkDesk({ store }), actor: values.actor ?? process.env.AWL_ACTOR });
65
- await server.connect(new StdioServerTransport());
66
- }
67
- async function main(argv) {
90
+ function main(argv) {
68
91
  const [command, ...rest] = argv;
69
92
  switch (command) {
93
+ case "new":
94
+ return newDocument(rest);
70
95
  case "validate":
71
96
  return validate(rest);
72
97
  case "desk":
73
- await desk(rest);
74
- return undefined; // keep running
98
+ throw new UsageError('"awl desk" moved to its own package in 0.2.0. Run: npx -y agent-work-layer-mcp --store <dir> --actor <agent-ref>');
75
99
  case "schema": {
76
100
  const which = rest[0];
77
101
  if (which !== "order" && which !== "report")
@@ -92,10 +116,10 @@ async function main(argv) {
92
116
  throw new UsageError(`unknown command "${command}"`);
93
117
  }
94
118
  }
95
- main(process.argv.slice(2)).then((code) => {
96
- if (code !== undefined)
97
- process.exitCode = code;
98
- }, (e) => {
119
+ try {
120
+ process.exitCode = main(process.argv.slice(2));
121
+ }
122
+ catch (e) {
99
123
  console.error(e instanceof UsageError ? `awl: ${e.message}\n\n${HELP}` : e);
100
124
  process.exitCode = 2;
101
- });
125
+ }
@@ -0,0 +1,6 @@
1
+ /** This package's version. */
2
+ export declare const VERSION: string;
3
+ /** The Work Order JSON Schema (schemas/work-order.schema.json). */
4
+ export declare const workOrderSchema: Record<string, unknown>;
5
+ /** The Work Report JSON Schema (schemas/work-report.schema.json). */
6
+ export declare const workReportSchema: Record<string, unknown>;
@@ -0,0 +1,453 @@
1
+ // Generated by scripts/generate.ts from package.json and schemas/*.json. Do not edit.
2
+ /** This package's version. */
3
+ export const VERSION = "0.2.0";
4
+ /** The Work Order JSON Schema (schemas/work-order.schema.json). */
5
+ export const workOrderSchema = {
6
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
7
+ "$id": "https://awl.dev/schemas/work-order-0.2.json",
8
+ "title": "AWL Work Order v0.2",
9
+ "type": "object",
10
+ "required": [
11
+ "spec",
12
+ "id",
13
+ "created_at",
14
+ "from",
15
+ "to",
16
+ "goal",
17
+ "criteria",
18
+ "budget",
19
+ "deadline",
20
+ "tools"
21
+ ],
22
+ "properties": {
23
+ "spec": {
24
+ "enum": [
25
+ "awl/work-order@0.1",
26
+ "awl/work-order@0.2"
27
+ ]
28
+ },
29
+ "id": {
30
+ "type": "string",
31
+ "minLength": 1,
32
+ "maxLength": 128
33
+ },
34
+ "created_at": {
35
+ "type": "string",
36
+ "format": "date-time"
37
+ },
38
+ "from": {
39
+ "$ref": "#/$defs/agentRef"
40
+ },
41
+ "to": {
42
+ "$ref": "#/$defs/agentRef"
43
+ },
44
+ "goal": {
45
+ "type": "string",
46
+ "minLength": 1
47
+ },
48
+ "criteria": {
49
+ "type": "array",
50
+ "minItems": 1,
51
+ "items": {
52
+ "type": "object",
53
+ "required": [
54
+ "id",
55
+ "text"
56
+ ],
57
+ "properties": {
58
+ "id": {
59
+ "type": "string",
60
+ "minLength": 1,
61
+ "maxLength": 64
62
+ },
63
+ "text": {
64
+ "type": "string",
65
+ "minLength": 1
66
+ }
67
+ },
68
+ "additionalProperties": false
69
+ }
70
+ },
71
+ "budget": {
72
+ "$ref": "#/$defs/amounts",
73
+ "minProperties": 1
74
+ },
75
+ "deadline": {
76
+ "type": "string",
77
+ "format": "date-time"
78
+ },
79
+ "tools": {
80
+ "type": "array",
81
+ "items": {
82
+ "$ref": "#/$defs/toolPattern"
83
+ }
84
+ },
85
+ "escalate_to": {
86
+ "$ref": "#/$defs/agentRef"
87
+ },
88
+ "inputs": {
89
+ "type": "object"
90
+ },
91
+ "context": {
92
+ "type": "array",
93
+ "items": {
94
+ "type": "object",
95
+ "required": [
96
+ "uri"
97
+ ],
98
+ "properties": {
99
+ "uri": {
100
+ "type": "string",
101
+ "minLength": 1
102
+ },
103
+ "note": {
104
+ "type": "string"
105
+ }
106
+ },
107
+ "additionalProperties": false
108
+ }
109
+ },
110
+ "handoff": {
111
+ "$ref": "#/$defs/handoff"
112
+ },
113
+ "output_schema": {
114
+ "type": "object"
115
+ },
116
+ "parent_id": {
117
+ "type": "string",
118
+ "minLength": 1
119
+ },
120
+ "metadata": {
121
+ "type": "object"
122
+ }
123
+ },
124
+ "patternProperties": {
125
+ "^x-": {}
126
+ },
127
+ "additionalProperties": false,
128
+ "$defs": {
129
+ "agentRef": {
130
+ "type": "string",
131
+ "minLength": 1,
132
+ "maxLength": 512
133
+ },
134
+ "toolPattern": {
135
+ "type": "string",
136
+ "pattern": "^(\\*|[A-Za-z0-9_.-]+/(\\*|[A-Za-z0-9_.-]+))$"
137
+ },
138
+ "amounts": {
139
+ "type": "object",
140
+ "properties": {
141
+ "usd": {
142
+ "type": "number",
143
+ "minimum": 0
144
+ },
145
+ "tokens": {
146
+ "type": "integer",
147
+ "minimum": 0
148
+ },
149
+ "tool_calls": {
150
+ "type": "integer",
151
+ "minimum": 0
152
+ },
153
+ "seconds": {
154
+ "type": "number",
155
+ "minimum": 0
156
+ }
157
+ },
158
+ "additionalProperties": false
159
+ },
160
+ "handoff": {
161
+ "type": "object",
162
+ "minProperties": 1,
163
+ "properties": {
164
+ "summary": {
165
+ "type": "string",
166
+ "minLength": 1
167
+ },
168
+ "decisions": {
169
+ "type": "array",
170
+ "items": {
171
+ "type": "object",
172
+ "required": [
173
+ "text"
174
+ ],
175
+ "properties": {
176
+ "text": {
177
+ "type": "string",
178
+ "minLength": 1
179
+ },
180
+ "why": {
181
+ "type": "string"
182
+ }
183
+ },
184
+ "additionalProperties": false
185
+ }
186
+ },
187
+ "tried": {
188
+ "type": "array",
189
+ "items": {
190
+ "type": "object",
191
+ "required": [
192
+ "text"
193
+ ],
194
+ "properties": {
195
+ "text": {
196
+ "type": "string",
197
+ "minLength": 1
198
+ },
199
+ "result": {
200
+ "type": "string"
201
+ }
202
+ },
203
+ "additionalProperties": false
204
+ }
205
+ },
206
+ "open_questions": {
207
+ "type": "array",
208
+ "items": {
209
+ "type": "string",
210
+ "minLength": 1
211
+ }
212
+ },
213
+ "next_step": {
214
+ "type": "string",
215
+ "minLength": 1
216
+ }
217
+ },
218
+ "additionalProperties": false
219
+ }
220
+ }
221
+ };
222
+ /** The Work Report JSON Schema (schemas/work-report.schema.json). */
223
+ export const workReportSchema = {
224
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
225
+ "$id": "https://awl.dev/schemas/work-report-0.2.json",
226
+ "title": "AWL Work Report v0.2",
227
+ "type": "object",
228
+ "required": [
229
+ "spec",
230
+ "order_id",
231
+ "from",
232
+ "status",
233
+ "result",
234
+ "evidence",
235
+ "cost",
236
+ "finished_at",
237
+ "tools_used"
238
+ ],
239
+ "properties": {
240
+ "spec": {
241
+ "enum": [
242
+ "awl/work-report@0.1",
243
+ "awl/work-report@0.2"
244
+ ]
245
+ },
246
+ "order_id": {
247
+ "type": "string",
248
+ "minLength": 1
249
+ },
250
+ "from": {
251
+ "type": "string",
252
+ "minLength": 1,
253
+ "maxLength": 512
254
+ },
255
+ "status": {
256
+ "enum": [
257
+ "done",
258
+ "blocked",
259
+ "failed"
260
+ ]
261
+ },
262
+ "result": {
263
+ "type": "object",
264
+ "required": [
265
+ "summary"
266
+ ],
267
+ "properties": {
268
+ "summary": {
269
+ "type": "string"
270
+ },
271
+ "output": {},
272
+ "artifacts": {
273
+ "type": "array",
274
+ "items": {
275
+ "type": "object",
276
+ "required": [
277
+ "uri"
278
+ ],
279
+ "properties": {
280
+ "uri": {
281
+ "type": "string",
282
+ "minLength": 1
283
+ },
284
+ "media_type": {
285
+ "type": "string"
286
+ },
287
+ "note": {
288
+ "type": "string"
289
+ }
290
+ },
291
+ "additionalProperties": false
292
+ }
293
+ }
294
+ },
295
+ "additionalProperties": false
296
+ },
297
+ "evidence": {
298
+ "type": "array",
299
+ "items": {
300
+ "type": "object",
301
+ "required": [
302
+ "criterion",
303
+ "type"
304
+ ],
305
+ "properties": {
306
+ "criterion": {
307
+ "type": "string",
308
+ "minLength": 1
309
+ },
310
+ "type": {
311
+ "enum": [
312
+ "tool_call",
313
+ "artifact",
314
+ "citation",
315
+ "note"
316
+ ]
317
+ },
318
+ "ref": {
319
+ "type": "string"
320
+ },
321
+ "note": {
322
+ "type": "string"
323
+ }
324
+ },
325
+ "additionalProperties": false
326
+ }
327
+ },
328
+ "cost": {
329
+ "type": "object",
330
+ "properties": {
331
+ "usd": {
332
+ "type": "number",
333
+ "minimum": 0
334
+ },
335
+ "tokens": {
336
+ "type": "integer",
337
+ "minimum": 0
338
+ },
339
+ "tool_calls": {
340
+ "type": "integer",
341
+ "minimum": 0
342
+ },
343
+ "seconds": {
344
+ "type": "number",
345
+ "minimum": 0
346
+ }
347
+ },
348
+ "additionalProperties": false
349
+ },
350
+ "started_at": {
351
+ "type": "string",
352
+ "format": "date-time"
353
+ },
354
+ "finished_at": {
355
+ "type": "string",
356
+ "format": "date-time"
357
+ },
358
+ "tools_used": {
359
+ "type": "array",
360
+ "items": {
361
+ "type": "string",
362
+ "pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
363
+ }
364
+ },
365
+ "attempt": {
366
+ "type": "integer",
367
+ "minimum": 1
368
+ },
369
+ "questions": {
370
+ "type": "array",
371
+ "items": {
372
+ "type": "string",
373
+ "minLength": 1
374
+ }
375
+ },
376
+ "error": {
377
+ "type": "string",
378
+ "minLength": 1
379
+ },
380
+ "handoff": {
381
+ "$ref": "#/$defs/handoff"
382
+ },
383
+ "metadata": {
384
+ "type": "object"
385
+ }
386
+ },
387
+ "patternProperties": {
388
+ "^x-": {}
389
+ },
390
+ "additionalProperties": false,
391
+ "$defs": {
392
+ "handoff": {
393
+ "type": "object",
394
+ "minProperties": 1,
395
+ "properties": {
396
+ "summary": {
397
+ "type": "string",
398
+ "minLength": 1
399
+ },
400
+ "decisions": {
401
+ "type": "array",
402
+ "items": {
403
+ "type": "object",
404
+ "required": [
405
+ "text"
406
+ ],
407
+ "properties": {
408
+ "text": {
409
+ "type": "string",
410
+ "minLength": 1
411
+ },
412
+ "why": {
413
+ "type": "string"
414
+ }
415
+ },
416
+ "additionalProperties": false
417
+ }
418
+ },
419
+ "tried": {
420
+ "type": "array",
421
+ "items": {
422
+ "type": "object",
423
+ "required": [
424
+ "text"
425
+ ],
426
+ "properties": {
427
+ "text": {
428
+ "type": "string",
429
+ "minLength": 1
430
+ },
431
+ "result": {
432
+ "type": "string"
433
+ }
434
+ },
435
+ "additionalProperties": false
436
+ }
437
+ },
438
+ "open_questions": {
439
+ "type": "array",
440
+ "items": {
441
+ "type": "string",
442
+ "minLength": 1
443
+ }
444
+ },
445
+ "next_step": {
446
+ "type": "string",
447
+ "minLength": 1
448
+ }
449
+ },
450
+ "additionalProperties": false
451
+ }
452
+ }
453
+ };
package/dist/types.d.ts CHANGED
@@ -1,6 +1,10 @@
1
- /** Document types for AWL v0.1. Field meanings are defined in SPEC.md §4 and §5. */
2
- export declare const ORDER_SPEC = "awl/work-order@0.1";
3
- export declare const REPORT_SPEC = "awl/work-report@0.1";
1
+ /** Document types for AWL v0.2. Field meanings are defined in SPEC.md §4 and §5. */
2
+ /** The spec version this package writes. */
3
+ export declare const ORDER_SPEC = "awl/work-order@0.2";
4
+ export declare const REPORT_SPEC = "awl/work-report@0.2";
5
+ /** Every spec version this package reads (SPEC.md §11). */
6
+ export declare const ORDER_SPECS: readonly ["awl/work-order@0.1", "awl/work-order@0.2"];
7
+ export declare const REPORT_SPECS: readonly ["awl/work-report@0.1", "awl/work-report@0.2"];
4
8
  /** An Agent Card URL, an agent name, or `human:<id>`. */
5
9
  export type AgentRef = string;
6
10
  /** Budget and cost units (SPEC.md §4.2). */
@@ -15,8 +19,22 @@ export interface ContextRef {
15
19
  uri: string;
16
20
  note?: string;
17
21
  }
22
+ /** What the next worker should know: decisions, dead ends and where work stands (SPEC.md §4.4). */
23
+ export interface Handoff {
24
+ summary?: string;
25
+ decisions?: {
26
+ text: string;
27
+ why?: string;
28
+ }[];
29
+ tried?: {
30
+ text: string;
31
+ result?: string;
32
+ }[];
33
+ open_questions?: string[];
34
+ next_step?: string;
35
+ }
18
36
  export interface WorkOrder {
19
- spec: typeof ORDER_SPEC;
37
+ spec: (typeof ORDER_SPECS)[number];
20
38
  id: string;
21
39
  created_at: string;
22
40
  from: AgentRef;
@@ -30,6 +48,7 @@ export interface WorkOrder {
30
48
  escalate_to?: AgentRef;
31
49
  inputs?: Record<string, unknown>;
32
50
  context?: ContextRef[];
51
+ handoff?: Handoff;
33
52
  output_schema?: Record<string, unknown>;
34
53
  parent_id?: string;
35
54
  metadata?: Record<string, unknown>;
@@ -54,7 +73,7 @@ export interface WorkResult {
54
73
  artifacts?: Artifact[];
55
74
  }
56
75
  export interface WorkReport {
57
- spec: typeof REPORT_SPEC;
76
+ spec: (typeof REPORT_SPECS)[number];
58
77
  order_id: string;
59
78
  from: AgentRef;
60
79
  status: ReportStatus;
@@ -68,6 +87,7 @@ export interface WorkReport {
68
87
  attempt?: number;
69
88
  questions?: string[];
70
89
  error?: string;
90
+ handoff?: Handoff;
71
91
  metadata?: Record<string, unknown>;
72
92
  [extension: `x-${string}`]: unknown;
73
93
  }
package/dist/types.js CHANGED
@@ -1,5 +1,9 @@
1
- /** Document types for AWL v0.1. Field meanings are defined in SPEC.md §4 and §5. */
2
- export const ORDER_SPEC = "awl/work-order@0.1";
3
- export const REPORT_SPEC = "awl/work-report@0.1";
1
+ /** Document types for AWL v0.2. Field meanings are defined in SPEC.md §4 and §5. */
2
+ /** The spec version this package writes. */
3
+ export const ORDER_SPEC = "awl/work-order@0.2";
4
+ export const REPORT_SPEC = "awl/work-report@0.2";
5
+ /** Every spec version this package reads (SPEC.md §11). */
6
+ export const ORDER_SPECS = ["awl/work-order@0.1", ORDER_SPEC];
7
+ export const REPORT_SPECS = ["awl/work-report@0.1", REPORT_SPEC];
4
8
  /** Budget and cost units (SPEC.md §4.2). */
5
9
  export const UNITS = ["usd", "tokens", "tool_calls", "seconds"];
@@ -1,7 +1,7 @@
1
+ import { workOrderSchema, workReportSchema } from "./generated.ts";
1
2
  import type { Finding, WorkOrder, WorkReport } from "./types.ts";
2
3
  /** The JSON Schemas shipped with this package. */
3
- export declare const workOrderSchema: Record<string, unknown>;
4
- export declare const workReportSchema: Record<string, unknown>;
4
+ export { workOrderSchema, workReportSchema };
5
5
  /** Does a concrete tool name (`server/tool`) match one of the allowed patterns? */
6
6
  export declare function toolAllowed(tool: string, patterns: readonly string[]): boolean;
7
7
  /** Is everything a child pattern allows also allowed by the parent's patterns? */
package/dist/validate.js CHANGED
@@ -1,7 +1,8 @@
1
- // Checks from SPEC.md §7 (orders and reports) and §8 (sub-orders).
2
- import { readFileSync } from "node:fs";
1
+ // Checks from SPEC.md §7 (orders and reports) and §8 (sub-orders). No Node built-ins here:
2
+ // the validator also runs in browsers, Deno and Bun (see src/validator.ts).
3
3
  import Ajv2020Module from "ajv/dist/2020.js";
4
4
  import addFormatsModule from "ajv-formats";
5
+ import { workOrderSchema, workReportSchema } from "./generated.js";
5
6
  import { UNITS } from "./types.js";
6
7
  // ajv and ajv-formats are CommonJS; their classes live on `.default` under NodeNext.
7
8
  const Ajv2020 = Ajv2020Module.default;
@@ -11,10 +12,8 @@ function newAjv() {
11
12
  addFormats(ajv);
12
13
  return ajv;
13
14
  }
14
- const loadSchema = (file) => JSON.parse(readFileSync(new URL(`../schemas/${file}`, import.meta.url), "utf8"));
15
15
  /** The JSON Schemas shipped with this package. */
16
- export const workOrderSchema = loadSchema("work-order.schema.json");
17
- export const workReportSchema = loadSchema("work-report.schema.json");
16
+ export { workOrderSchema, workReportSchema };
18
17
  const ajv = newAjv();
19
18
  const orderShape = ajv.compile(workOrderSchema);
20
19
  const reportShape = ajv.compile(workReportSchema);
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Validation only: document types, JSON Schemas and every check in SPEC.md §7–§8.
3
+ * Imports no Node built-ins, so it runs in browsers, Deno and Bun.
4
+ * @packageDocumentation
5
+ */
6
+ export * from "./types.ts";
7
+ export { validateOrder, validateReport, validateChildOrder, hasInvalid, isWorkOrder, isWorkReport, toolAllowed, patternCovered, workOrderSchema, workReportSchema, } from "./validate.ts";
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Validation only: document types, JSON Schemas and every check in SPEC.md §7–§8.
3
+ * Imports no Node built-ins, so it runs in browsers, Deno and Bun.
4
+ * @packageDocumentation
5
+ */
6
+ export * from "./types.js";
7
+ export { validateOrder, validateReport, validateChildOrder, hasInvalid, isWorkOrder, isWorkReport, toolAllowed, patternCovered, workOrderSchema, workReportSchema, } from "./validate.js";
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- /** This package's version, read from package.json. */
2
- export declare const VERSION: string;
1
+ /** This package's version, from package.json (written into src/generated.ts at build time). */
2
+ export { VERSION } from "./generated.ts";
package/dist/version.js CHANGED
@@ -1,3 +1,2 @@
1
- import { readFileSync } from "node:fs";
2
- /** This package's version, read from package.json. */
3
- export const VERSION = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")).version;
1
+ /** This package's version, from package.json (written into src/generated.ts at build time). */
2
+ export { VERSION } from "./generated.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-work-layer",
3
- "version": "0.1.1",
4
- "description": "Work Orders and Work Reports for AI agents: a typed SDK, validator, lifecycle desk and MCP server.",
3
+ "version": "0.2.0",
4
+ "description": "Work Orders and Work Reports for AI agents: a typed SDK, validator and lifecycle desk. The MCP server is in agent-work-layer-mcp.",
5
5
  "keywords": [
6
6
  "ai",
7
7
  "agents",
@@ -26,9 +26,9 @@
26
26
  "types": "./dist/index.d.ts",
27
27
  "import": "./dist/index.js"
28
28
  },
29
- "./mcp": {
30
- "types": "./dist/mcp.d.ts",
31
- "import": "./dist/mcp.js"
29
+ "./validate": {
30
+ "types": "./dist/validator.d.ts",
31
+ "import": "./dist/validator.js"
32
32
  },
33
33
  "./schemas/work-order.schema.json": "./schemas/work-order.schema.json",
34
34
  "./schemas/work-report.schema.json": "./schemas/work-report.schema.json",
@@ -40,28 +40,25 @@
40
40
  "files": [
41
41
  "dist",
42
42
  "schemas",
43
+ "README.md",
44
+ "LICENSE",
43
45
  "SPEC.md",
44
46
  "CHANGELOG.md"
45
47
  ],
46
48
  "sideEffects": false,
47
49
  "scripts": {
50
+ "generate": "node scripts/generate.ts",
48
51
  "build": "rm -rf dist && tsc -p tsconfig.build.json && chmod +x dist/cli.js",
49
52
  "typecheck": "tsc --noEmit -p tsconfig.json",
50
53
  "test": "node --test \"test/**/*.test.ts\"",
51
54
  "test:coverage": "node --test --experimental-test-coverage --test-coverage-include=\"src/**\" \"test/**/*.test.ts\"",
52
- "test:package": "npm run build && node scripts/package-smoke.ts",
53
- "check": "npm run typecheck && npm test && npm run test:package",
54
- "prepublishOnly": "npm run check"
55
+ "version": "node scripts/generate.ts",
56
+ "prepack": "cp ../../README.md ../../LICENSE ../../SPEC.md ../../CHANGELOG.md . && rm -rf schemas && cp -R ../../schemas schemas",
57
+ "prepublishOnly": "npm run build"
55
58
  },
56
59
  "dependencies": {
57
- "@modelcontextprotocol/sdk": "^1.32.1",
58
60
  "ajv": "^8.20.0",
59
- "ajv-formats": "^3.0.1",
60
- "zod": "^3.25.76"
61
- },
62
- "devDependencies": {
63
- "@types/node": "^22.20.5",
64
- "typescript": "^7.0.2"
61
+ "ajv-formats": "^3.0.1"
65
62
  },
66
63
  "publishConfig": {
67
64
  "access": "public"
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://awl.dev/schemas/work-order-0.1.json",
4
- "title": "AWL Work Order v0.1",
3
+ "$id": "https://awl.dev/schemas/work-order-0.2.json",
4
+ "title": "AWL Work Order v0.2",
5
5
  "type": "object",
6
6
  "required": ["spec", "id", "created_at", "from", "to", "goal", "criteria", "budget", "deadline", "tools"],
7
7
  "properties": {
8
- "spec": { "const": "awl/work-order@0.1" },
8
+ "spec": { "enum": ["awl/work-order@0.1", "awl/work-order@0.2"] },
9
9
  "id": { "type": "string", "minLength": 1, "maxLength": 128 },
10
10
  "created_at": { "type": "string", "format": "date-time" },
11
11
  "from": { "$ref": "#/$defs/agentRef" },
@@ -38,6 +38,7 @@
38
38
  "additionalProperties": false
39
39
  }
40
40
  },
41
+ "handoff": { "$ref": "#/$defs/handoff" },
41
42
  "output_schema": { "type": "object" },
42
43
  "parent_id": { "type": "string", "minLength": 1 },
43
44
  "metadata": { "type": "object" }
@@ -56,6 +57,34 @@
56
57
  "seconds": { "type": "number", "minimum": 0 }
57
58
  },
58
59
  "additionalProperties": false
60
+ },
61
+ "handoff": {
62
+ "type": "object",
63
+ "minProperties": 1,
64
+ "properties": {
65
+ "summary": { "type": "string", "minLength": 1 },
66
+ "decisions": {
67
+ "type": "array",
68
+ "items": {
69
+ "type": "object",
70
+ "required": ["text"],
71
+ "properties": { "text": { "type": "string", "minLength": 1 }, "why": { "type": "string" } },
72
+ "additionalProperties": false
73
+ }
74
+ },
75
+ "tried": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "object",
79
+ "required": ["text"],
80
+ "properties": { "text": { "type": "string", "minLength": 1 }, "result": { "type": "string" } },
81
+ "additionalProperties": false
82
+ }
83
+ },
84
+ "open_questions": { "type": "array", "items": { "type": "string", "minLength": 1 } },
85
+ "next_step": { "type": "string", "minLength": 1 }
86
+ },
87
+ "additionalProperties": false
59
88
  }
60
89
  }
61
90
  }
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://awl.dev/schemas/work-report-0.1.json",
4
- "title": "AWL Work Report v0.1",
3
+ "$id": "https://awl.dev/schemas/work-report-0.2.json",
4
+ "title": "AWL Work Report v0.2",
5
5
  "type": "object",
6
6
  "required": ["spec", "order_id", "from", "status", "result", "evidence", "cost", "finished_at", "tools_used"],
7
7
  "properties": {
8
- "spec": { "const": "awl/work-report@0.1" },
8
+ "spec": { "enum": ["awl/work-report@0.1", "awl/work-report@0.2"] },
9
9
  "order_id": { "type": "string", "minLength": 1 },
10
10
  "from": { "type": "string", "minLength": 1, "maxLength": 512 },
11
11
  "status": { "enum": ["done", "blocked", "failed"] },
@@ -61,8 +61,39 @@
61
61
  "attempt": { "type": "integer", "minimum": 1 },
62
62
  "questions": { "type": "array", "items": { "type": "string", "minLength": 1 } },
63
63
  "error": { "type": "string", "minLength": 1 },
64
+ "handoff": { "$ref": "#/$defs/handoff" },
64
65
  "metadata": { "type": "object" }
65
66
  },
66
67
  "patternProperties": { "^x-": {} },
67
- "additionalProperties": false
68
+ "additionalProperties": false,
69
+ "$defs": {
70
+ "handoff": {
71
+ "type": "object",
72
+ "minProperties": 1,
73
+ "properties": {
74
+ "summary": { "type": "string", "minLength": 1 },
75
+ "decisions": {
76
+ "type": "array",
77
+ "items": {
78
+ "type": "object",
79
+ "required": ["text"],
80
+ "properties": { "text": { "type": "string", "minLength": 1 }, "why": { "type": "string" } },
81
+ "additionalProperties": false
82
+ }
83
+ },
84
+ "tried": {
85
+ "type": "array",
86
+ "items": {
87
+ "type": "object",
88
+ "required": ["text"],
89
+ "properties": { "text": { "type": "string", "minLength": 1 }, "result": { "type": "string" } },
90
+ "additionalProperties": false
91
+ }
92
+ },
93
+ "open_questions": { "type": "array", "items": { "type": "string", "minLength": 1 } },
94
+ "next_step": { "type": "string", "minLength": 1 }
95
+ },
96
+ "additionalProperties": false
97
+ }
98
+ }
68
99
  }
package/dist/mcp.d.ts DELETED
@@ -1,13 +0,0 @@
1
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import type { WorkDesk } from "./desk.ts";
3
- export interface DeskServerOptions {
4
- desk: WorkDesk;
5
- /**
6
- * Fixes who this server acts as. When set, tools act as this identity and refuse any
7
- * other `actor` argument, so a worker session cannot approve its own work.
8
- */
9
- actor?: string;
10
- name?: string;
11
- }
12
- /** Builds an MCP server exposing the desk's operations as tools. Connect it to any transport. */
13
- export declare function createDeskServer({ desk, actor: sessionActor, name }: DeskServerOptions): McpServer;
package/dist/mcp.js DELETED
@@ -1,116 +0,0 @@
1
- // The desk as an MCP server (SPEC.md §9, MCP binding).
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { z } from "zod";
4
- import { AwlError } from "./errors.js";
5
- import { STATES } from "./lifecycle.js";
6
- import { summarize } from "./desk.js";
7
- import { VERSION } from "./version.js";
8
- const amounts = z
9
- .object({
10
- usd: z.number().min(0).optional(),
11
- tokens: z.number().int().min(0).optional(),
12
- tool_calls: z.number().int().min(0).optional(),
13
- seconds: z.number().min(0).optional(),
14
- })
15
- .strict();
16
- const actorArg = z.string().optional().describe("Who is acting. Omit to use this session's identity.");
17
- const idArg = z.string().describe("Work order id");
18
- const view = (r) => ({ ...summarize(r), order: r.order, latest_report: r.reports.at(-1) ?? null, findings: r.findings, events: r.events });
19
- /** Drops keys whose value is undefined so they don't reach JSON Schema validation. */
20
- const defined = (o) => JSON.parse(JSON.stringify(o));
21
- async function respond(fn) {
22
- try {
23
- const value = await fn();
24
- return { content: [{ type: "text", text: JSON.stringify(value, null, 2) }] };
25
- }
26
- catch (e) {
27
- const body = e instanceof AwlError ? e.toJSON() : { code: "INTERNAL", message: String(e) };
28
- return { isError: true, content: [{ type: "text", text: JSON.stringify(body, null, 2) }] };
29
- }
30
- }
31
- /** Builds an MCP server exposing the desk's operations as tools. Connect it to any transport. */
32
- export function createDeskServer({ desk, actor: sessionActor, name = "awl-work-desk" }) {
33
- const server = new McpServer({ name, version: VERSION });
34
- const who = (arg) => {
35
- if (sessionActor && arg && arg !== sessionActor)
36
- throw new AwlError("FORBIDDEN", `this session acts as "${sessionActor}", not "${arg}"`);
37
- const actor = arg ?? sessionActor;
38
- if (!actor)
39
- throw new AwlError("BAD_REQUEST", 'no actor: pass "actor" or start the desk with an identity');
40
- return actor;
41
- };
42
- server.registerTool("create_work_order", {
43
- description: "Issue a Work Order to a worker agent. Every criterion must be provable with evidence. " +
44
- "Set parent_id to split your own order into a sub-order; it may only narrow budget, tools and deadline.",
45
- inputSchema: {
46
- actor: actorArg,
47
- to: z.string().describe("Worker agent reference (Agent Card URL or name)"),
48
- goal: z.string().describe("What to achieve, in plain language"),
49
- criteria: z.array(z.object({ id: z.string(), text: z.string() })).min(1).describe("How done will be judged"),
50
- budget: amounts.describe("Upper limits; set at least one unit"),
51
- deadline: z.string().describe("RFC 3339 timestamp"),
52
- tools: z.array(z.string()).describe('Allowed tools: "server/tool", "server/*" or "*"'),
53
- escalate_to: z.string().optional(),
54
- inputs: z.record(z.any()).optional(),
55
- context: z.array(z.object({ uri: z.string(), note: z.string().optional() })).optional(),
56
- output_schema: z.record(z.any()).optional().describe("JSON Schema that result.output must match"),
57
- parent_id: z.string().optional(),
58
- id: z.string().optional().describe("Optional explicit id"),
59
- metadata: z.record(z.any()).optional(),
60
- },
61
- }, async ({ actor, ...input }) => respond(async () => view(await desk.create(defined(input), who(actor)))));
62
- server.registerTool("list_work_orders", {
63
- description: "List work orders. Workers: filter by to=<yourself> and state=offered to find new jobs.",
64
- inputSchema: {
65
- to: z.string().optional(),
66
- from: z.string().optional(),
67
- state: z.enum(STATES).optional(),
68
- parent_id: z.string().optional(),
69
- },
70
- }, async (filter) => respond(() => desk.list(defined(filter))));
71
- server.registerTool("get_work_order", { description: "Full order, current state, latest report, check findings and event log.", inputSchema: { id: idArg } }, async ({ id }) => respond(async () => view(await desk.get(id))));
72
- server.registerTool("accept_work_order", { description: "Worker accepts an offered order.", inputSchema: { id: idArg, actor: actorArg } }, async ({ id, actor }) => respond(async () => view(await desk.accept(id, who(actor)))));
73
- server.registerTool("decline_work_order", { description: "Worker declines an offered order.", inputSchema: { id: idArg, actor: actorArg, reason: z.string().optional() } }, async ({ id, actor, reason }) => respond(async () => view(await desk.decline(id, who(actor), reason))));
74
- server.registerTool("start_work_order", { description: "Worker starts an accepted order.", inputSchema: { id: idArg, actor: actorArg } }, async ({ id, actor }) => respond(async () => view(await desk.start(id, who(actor)))));
75
- server.registerTool("submit_work_report", {
76
- description: "Worker reports on an order. done needs evidence for every criterion; blocked needs questions; failed needs error. " +
77
- "A rejected report returns the failed checks so you can fix it and resubmit.",
78
- inputSchema: {
79
- id: idArg,
80
- actor: actorArg,
81
- status: z.enum(["done", "blocked", "failed"]),
82
- result: z.object({
83
- summary: z.string(),
84
- output: z.any().optional(),
85
- artifacts: z.array(z.object({ uri: z.string(), media_type: z.string().optional(), note: z.string().optional() })).optional(),
86
- }),
87
- evidence: z.array(z.object({
88
- criterion: z.string(),
89
- type: z.enum(["tool_call", "artifact", "citation", "note"]),
90
- ref: z.string().optional(),
91
- note: z.string().optional(),
92
- })),
93
- cost: amounts,
94
- tools_used: z.array(z.string()).describe('Exact tool names called, as "server/tool"'),
95
- started_at: z.string().optional(),
96
- finished_at: z.string().optional(),
97
- questions: z.array(z.string()).optional(),
98
- error: z.string().optional(),
99
- metadata: z.record(z.any()).optional(),
100
- },
101
- }, async ({ id, actor, ...input }) => respond(async () => {
102
- const { record, findings } = await desk.submitReport(id, defined(input), who(actor));
103
- return { ...summarize(record), findings };
104
- }));
105
- server.registerTool("answer_question", { description: "Manager or escalation contact answers a blocked worker; the order returns to working.", inputSchema: { id: idArg, actor: actorArg, answer: z.string() } }, async ({ id, actor, answer }) => respond(async () => view(await desk.answer(id, who(actor), answer))));
106
- server.registerTool("review_work_report", {
107
- description: "Manager approves a submitted report, or sends it back for rework with a reason.",
108
- inputSchema: { id: idArg, actor: actorArg, decision: z.enum(["approve", "rework"]), reason: z.string().optional() },
109
- }, async ({ id, actor, decision, reason }) => respond(async () => view(await desk.review(id, who(actor), decision, reason))));
110
- server.registerTool("cancel_work_order", { description: "Manager cancels an order that is not finished.", inputSchema: { id: idArg, actor: actorArg, reason: z.string().optional() } }, async ({ id, actor, reason }) => respond(async () => view(await desk.cancel(id, who(actor), reason))));
111
- server.registerTool("wait_for_update", {
112
- description: "Wait until the order changes after since_seq (up to timeout_s seconds). Use it to follow a job live.",
113
- inputSchema: { id: idArg, since_seq: z.number().int().min(0), timeout_s: z.number().min(1).max(55).optional() },
114
- }, async ({ id, since_seq, timeout_s }, extra) => respond(async () => view(await desk.waitForChange(id, since_seq, { timeoutMs: (timeout_s ?? 25) * 1000, signal: extra.signal }))));
115
- return server;
116
- }