agent-work-layer

中文文档
0.9.1发布于 2026/11/03Apache-2.0TypeScriptES Module构建来源已验证

Work Orders and Work Reports for AI agents: a typed SDK, validator and lifecycle desk. The MCP server is in agent-work-layer-mcp.

原始使用文档
文档目录19 节
  1. How it works
  2. Install
  3. Quick start
  4. Hand off unfinished work
  5. Hold writes until the report is approved
  6. Follow progress and track records
  7. Validate documents
  8. Run as an MCP server
  9. CLI
  10. Lifecycle
  11. API
  12. Error codes
  13. Custom storage
  14. Production notes
  15. Authenticated actor
  16. Delegation Pass
  17. Development
  18. Contributing
  19. License

agent-work-layer

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.

npm license M8ven Score

  • Work Order (manager → worker): the goal, how "done" is judged, budget, deadline, allowed tools.
  • Work Report (worker → manager): the result, evidence for each criterion, actual cost, tools used.

Agent Cards say who an agent is. A2A carries messages between agents. MCP connects agents to tools. AWL describes the job itself, so any manager can hand work to any worker and check what came back. The format is defined in SPEC.md.

Manager and worker agents connected by Agent Card, A2A and the AWL work layer; the worker reaches tools through MCP

How it works

  1. A manager issues a Work Order: goal, criteria, budget, deadline, allowed tools.
  2. A worker accepts it and does the job with its tools.
  3. The worker returns a Work Report with evidence for every criterion and what it spent.
  4. The desk checks the report against the order. The manager approves it or sends it back.

Each Work Report field answers a Work Order field: evidence proves criteria, cost stays within budget, tools used stay within allowed tools

More diagrams: message sequence · lifecycle · delegation · handoff · staged tools · authenticated actor · Delegation Pass · JWS vs JWT

Install

Install only the part you use:

You want to…InstallPackages
Validate orders and reports, or run a desk in your codenpm install agent-work-layer7
Validate in a browser, Deno or Bunnpm install agent-work-layer, then import agent-work-layer/validate7
Validate orders and reports in Pythonpip install agent-work-layer (python/)11
Connect agent sessions (e.g. Claude Code) through MCPnpx -y agent-work-layer-mcp (setup)~100
Sign or verify Delegation Passesnpm install agent-work-layer (how)7

Node.js 20.11 or later. ESM only. Types included. The Python package needs Python 3.9 or later.

Quick start

import { WorkDesk } from "agent-work-layer";

const desk = new WorkDesk(); // in-memory; use FileStore, SqliteStore or your own DeskStore to persist

// Manager issues a job
const { order } = await desk.create(
  {
    to: "agent:researcher",
    goal: "Summarize the main risk factors in the latest annual report",
    criteria: [
      { id: "c1", text: "Lists three risk factors, each quoted from the filing" },
      { id: "c2", text: "States total debt and its fiscal year" },
    ],
    budget: { usd: 2, tool_calls: 20 },
    deadline: new Date(Date.now() + 3_600_000).toISOString(),
    tools: ["filings/*"],
  },
  "agent:manager",
);

// Worker accepts, does the work, reports with evidence
await desk.accept(order.id, "agent:researcher");
const { record } = await desk.submitReport(
  order.id,
  {
    status: "done",
    result: { summary: "Refinancing, customer concentration and FX risk. Debt 1.2B USD (FY2025)." },
    evidence: [
      { criterion: "c1", type: "tool_call", ref: "filings/get_section#3" },
      { criterion: "c2", type: "citation", ref: "filings/get_section#5" },
    ],
    cost: { usd: 0.84, tool_calls: 7 },
    tools_used: ["filings/search", "filings/get_section"],
  },
  "agent:researcher",
);

// Manager approves, or sends it back with a reason
await desk.review(order.id, "agent:manager", "approve");

For a runnable version, clone the repo, run npm install, then run node examples/two-agents.ts (Node 22.18+). examples/two-agents.ts walks one job through a rejected report, a rework request and approval, printing each event as it happens.

The desk enforces the spec as you go:

  • Who may do what: only the worker reports, and only the manager approves.
  • Which state changes are legal.
  • Evidence for every criterion.
  • Only allowed tools.
  • Cost within budget and finishing before the deadline. Going over either expires the order.
  • Sub-orders can only narrow their parent's authority. Their budgets come out of the parent's, so a whole tree of sub-orders never spends more than the order at its root.
  • A reissued order can only continue a finished one, so one job never has two live orders.

Optionally, it also signs the worker's authority into a Delegation Pass that gateways and other systems can check without the desk.

Hand off unfinished work

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:

Worker A reports failed with a handoff; the manager cancels that order and reissues the job to Worker B with continues pointing at it and the same handoff: summary, decisions, what was tried, open questions and next step

await desk.submitReport(order.id, {
  status: "failed",
  error: "Export runs out of memory on files over 2 GB",
  handoff: {
    decisions: [{ text: "Stream rows instead of buffering", why: "Memory limit" }],
    tried: [{ text: "xlsx library", result: "Too slow on large files" }],
    next_step: "Add tests for null cells",
  },
  /* result, evidence, cost, tools_used */
}, "agent:researcher");

const { handoff } = (await desk.get(order.id)).reports.at(-1)!;
await desk.cancel(order.id, "agent:manager", "reissuing");   // a continued order must be finished
await desk.create({ ...nextJob, to: "agent:other-worker", handoff, continues: order.id }, "agent:manager");

await desk.list({ continues: order.id });   // the orders that pick up from this one

context lists what to read; handoff says what was decided and already tried; continues links the new order to the one it replaces, so the chain of orders is the job's full history. Unlike parent_id, it doesn't narrow anything: the new order can have a different worker, budget and tools. See SPEC.md §4.4 and §4.5.

Hold writes until the report is approved

Rejecting a report doesn't unsend an email. For tools whose effects you want to review first, list them in staged:

await desk.create({ ...job, tools: ["filings/*", "gmail/*"], staged: ["gmail/send"] }, "agent:manager");

The worker's read, filings/search, goes through the gateway to the real server at once. Its write, gmail/send, is held by the gateway, counted and logged, and the worker gets pending:gmail/send#3. The worker reports with the call in effects; on approve, awl-desk gateway --commit runs it once; on rework it is dropped and nothing has to be undone

The worker's MCP gateway then holds back calls to gmail/send: it counts and logs each one and returns a placeholder (pending:gmail/send#4). The worker lists them in its report's effects, which the manager reviews with the rest of the report. Approving lets the gateway run them; rework drops them, and since they never ran, there is nothing to undo. Reads still run normally. A sub-order can't allow a tool its parent stages (C5).

A staged call can use an earlier one's result: pass { "$ref": "github/create_issue#3", "pointer": "/structuredContent/number" } as an argument, and the gateway fills it in when it runs the effects. The manager can also approve only some effects:

await desk.review(order.id, "agent:manager", "approve", "Send the CFO copy only", { effects: ["gmail/send#4"] });

See SPEC.md §4.6.

Follow progress and track records

await desk.progress(order.id, "agent:researcher", "Risk section read; checking the debt note", 60);
await desk.trackRecord("agent:researcher");
// { orders: 12, approved: 9, approved_first_time: 7, reworks: 3, expired: 1, ..., spent: { usd: 8.4 }, budget: { usd: 24 } }

Progress notes are progress events: they change no state, but waitForChange and wait_for_update wake on them. A track record counts what happened to the orders a desk holds for one worker. See SPEC.md §6.2 and §6.3.

Validate documents

import { validateOrder, validateReport, hasInvalid } from "agent-work-layer";

const findings = validateReport(report, order);
// [{ rule: "R4", kind: "invalid", path: "/evidence", message: 'no evidence for criterion "c2"' }, ...]
if (hasInvalid(findings)) reject(findings);

Every finding carries the rule id from SPEC.md §7. invalid findings reject a document. limit findings (over budget, past deadline) are recorded and expire the order.

Writing a validator in another language? Run it against the JSON test vectors in conformance/: documents with the findings each rule must report.

The JSON Schemas are exported too: workOrderSchema, workReportSchema, or agent-work-layer/schemas/work-order.schema.json.

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.)

Run as an MCP server

The server lives in its own package, agent-work-layer-mcp, so people who only validate documents don't download the MCP SDK.

For development, over stdio. Each agent session runs its own desk process. Sessions share one folder, and each is locked to the identity it is started with.

# Claude Code: a manager session and a worker session, each added in its own project folder
claude mcp add awl -e AWL_STORE="$HOME/.awl" -e AWL_ACTOR=agent:manager -- npx -y agent-work-layer-mcp
claude mcp add awl -e AWL_STORE="$HOME/.awl" -e AWL_ACTOR=agent:worker  -- npx -y agent-work-layer-mcp

For shared use, over HTTP. One desk serves every agent, and each request's credential decides who it acts as, so a worker cannot approve its own work (details):

npx -y agent-work-layer-mcp serve --keys keys.json        # { "keys": { "<key>": "agent:manager", ... } }
claude mcp add --transport http awl http://127.0.0.1:8787/mcp --header "Authorization: Bearer $AWL_KEY"

Tools: create_work_order, list_work_orders, get_work_order, accept_work_order, decline_work_order, start_work_order, report_progress, submit_work_report, answer_question, review_work_report, allow_effect_retry, cancel_work_order, wait_for_update, get_track_record, and renew_work_pass when the desk issues Delegation Passes.

To enforce an order while the worker works, and to hold its staged writes until you approve, connect the worker to its real MCP servers through the gateway: npx -y agent-work-layer-mcp gateway --order <id> --config upstream.json. After approval, --commit runs the approved writes. See the gateway docs.

To embed the server in your own process or transport (npm install agent-work-layer agent-work-layer-mcp):

import { WorkDesk, FileStore } from "agent-work-layer";
import { createDeskServer } from "agent-work-layer-mcp";

const server = createDeskServer({ desk: new WorkDesk({ store: new FileStore("./.awl") }), actor: "agent:manager" });
await server.connect(transport);

CLI

npx agent-work-layer new order > order.json
npx agent-work-layer new report > report.json
npx agent-work-layer validate order.json report.json      # exit 1 on invalid, prints rule ids
npx agent-work-layer validate child.json --parent order.json
npx agent-work-layer validate order.json report.json --json
npx agent-work-layer schema order
npx -y agent-work-layer-mcp --store ./.awl --actor agent:worker   # the MCP desk on stdio (was "awl desk" before 0.2)
npx -y agent-work-layer-mcp serve --keys keys.json                 # one shared desk over HTTP; add --pass-key/--issuer to sign passes
npx -y agent-work-layer-mcp gateway --order <id> --config <file>   # enforce an order at every tool call; add --pass-keys to use passes

new order starts with one criterion, a USD 1 budget, no allowed tools and a deadline one hour after creation. new report starts as blocked, with a question and no evidence of completed work. Both templates validate together as printed. Replace the placeholder goal, identities, criterion and report contents before using them; keep the report's order_id equal to the order's id.

Lifecycle

offered ──accept──▶ accepted ──start──▶ working ──report done/failed──▶ submitted ──approve──▶ approved
   │                                     │  ▲                              │
 decline                         blocked │  │ answer                       │ rework (with reason)
   ▼                                     ▼  │                              ▼
declined                                blocked                          working

Any active state ──cancel──▶ cancelled   ·   deadline or budget exceeded ──▶ expired

While working, the worker can post progress notes. They are recorded as events but don't change the state (SPEC.md §6.2).

ActionWho
accept, decline, start, submit report, post progressthe worker (order.to)
record or settle effect outcomes, after approvalthe worker
approve, rework, cancel, allow an effect retrythe manager (order.from)
answer a blocked workerthe manager or order.escalate_to
expirethe desk

API

ExportPurpose
WorkDeskRuns the lifecycle: create, accept, decline, start, progress, submitReport, answer, review, cancel, get, list, subOrders, held, trackRecord, waitForChange, and issuePass when it issues passes. After approval: recordCommit, settle (worker) and allowRetry (manager)
MemoryStore, FileStore, SqliteStore, DeskStoreStorage. Implement DeskStore (4 methods) to use a database
validateOrder, validateReport, validateChildOrder, validateChildBudgets, validateOwnCost, hasInvalidSpec checks, returning Finding[]
heldBySubOrdersThe budget an order's sub-orders hold, which it may not spend itself
isWorkOrder, isWorkReportType guards
toolAllowed, patternCoveredTool pattern matching
effectReferencesThe references to earlier effects' results in an effect's arguments
approvedEffects, effectOutcomesThe effects an approval lets run, and what is known of each from the order's commit and settle events
nextState, allowedActions, STATES, ROLE_FOR, isTerminalThe state machine
AwlError, isAwlErrorTyped errors with code and findings
signPass, verifyPass, decodePass, PassErrorDelegation Passes: sign, verify (including narrowing against a parent), read
orderSpecFor, reportSpecForThe lowest version that has every field a document uses, as the desk writes it
ORDER_SPEC, REPORT_SPEC, ORDER_SPECS, REPORT_SPECSThe newest spec version, and every version read
createDeskServer (from agent-work-layer-mcp)MCP server for a desk
createDeskHttpHandler, apiKeyAuth, bearerAuth, AuthError (from agent-work-layer-mcp)The desk over HTTP, with the actor from each request's credential
createGateway (from agent-work-layer-mcp)MCP gateway enforcing an order, or a Delegation Pass, at every tool call

WorkDesk options: store, clock (for tests), newId, onEvent(event, record) (webhooks, logging, metrics), passes: { key, issuer, ttlSeconds? } (sign Delegation Passes).

Error codes

NOT_FOUND, ALREADY_EXISTS, FORBIDDEN, INVALID_TRANSITION, VALIDATION_FAILED (with findings), CONFLICT (a concurrent writer got there first; re-read and retry), BAD_REQUEST.

PassError codes: MALFORMED, UNSUPPORTED, BAD_SIGNATURE, EXPIRED, NOT_YET_VALID, WRONG_ISSUER, WRONG_WORKER, WRONG_ORDER, WRONG_PARENT, WIDER_THAN_PARENT (with findings from C1–C3 and C5).

Custom storage

MemoryStore keeps records in memory, FileStore writes one JSON file per order, and SqliteStore keeps them in a SQLite database using Node's built-in node:sqlite (Node 22.13+, no extra dependency):

import { WorkDesk, SqliteStore } from "agent-work-layer";

const desk = new WorkDesk({ store: new SqliteStore("./awl.db") }); // or ":memory:"

To use another database, implement DeskStore:

import type { DeskStore, OrderRecord } from "agent-work-layer";

class PostgresStore implements DeskStore {
  async read(id: string): Promise<OrderRecord | null> { /* SELECT */ }
  async insert(record: OrderRecord) { /* INSERT, throw AwlError("ALREADY_EXISTS") on conflict */ }
  async update(record: OrderRecord, expectedSeq: number) { /* UPDATE ... WHERE last_seq = expectedSeq, else AwlError("CONFLICT") */ }
  async list() { /* SELECT */ }
}

Production notes

A stdio desk trusts the actor it is started with. In production, run one desk over HTTP that takes the actor from each request's credential: npx -y agent-work-layer-mcp serve --keys keys.json, or createDeskHttpHandler with your own authenticate hook (OAuth 2.1 bearer tokens, API keys, mTLS). See the MCP package README. For real-time enforcement, run the MCP gateway (npx -y agent-work-layer-mcp gateway), which checks tools and budget at each tool call, not only when the report arrives. When the gateway or another team's system can't read the desk, give it Delegation Passes instead. See SPEC.md §9.1 and §10.

Authenticated actor

With stdio, each agent runs its own desk and declares its own actor, so a worker could start one as its manager. Over HTTP, every agent connects to one desk, which takes the actor from the request's credential through a pluggable authenticate(request) hook. Teams plug in what they already use: OAuth 2.1 bearer tokens, API keys or mTLS. An actor argument naming anyone else is refused (#16, SPEC.md §10.1).

With stdio, a worker can start its own desk with --actor agent:manager and approve its own report. Over HTTP, agents send a credential to one desk, authenticate(request) maps it to an agent ref before the role check, and a spoofed actor argument is refused

Delegation Pass

When a worker starts, the desk signs a short-lived pass (a JWS) carrying the order's tools, budget and deadline. The worker presents it with each tool call, and a gateway or another team's system verifies it on its own, without calling the desk. A sub-order gets a narrower pass that names its parent, so the delegation rules (C1–C3, C5) can be checked offline (#17, SPEC.md §10.2).

The desk signs a pass when the worker starts; the gateway verifies it on each tool call, allowing filings/search and refusing web/browse; a sub-order gets a narrower pass naming its parent. Below, the claims in a pass: iss, sub, awl_order, awl_tools, awl_budget, awl_parent, awl_deadline, awl_attempt, iat, exp, jti, and the typ awl-pass+jwt

  • A pass lasts 15 minutes by default, never past the deadline. The desk issues a new one each time the order enters working, and the worker renews it while it works.
  • It carries the order's whole budget; the gateway tracks what has been spent.
  • Cancelling or expiring the order stops new passes. There is no other revocation, which is why passes are short.
  • A pass is a JWS whose payload is a claims set, which makes it a JWT too. This diagram shows a pass's three parts and the order to verify them in.
import { WorkDesk, verifyPass } from "agent-work-layer";

const desk = new WorkDesk({ passes: { key: privateJwk, issuer: "https://desk.example.com" } });
// ...create, accept...
const { pass } = await desk.start(id, "agent:worker");   // renew with desk.issuePass(id, "agent:worker")

// Anywhere else, with only the desk's public key:
const verified = await verifyPass(pass, publicJwk, { issuer: "https://desk.example.com", worker: "agent:worker", order: id });
verified.allows("filings/search");   // true
const child = await verifyPass(childPass, publicJwk, { parent: verified });   // throws WIDER_THAN_PARENT if it breaks C1–C3 or C5

Keys are P-256 or Ed25519 JWKs (or WebCrypto keys) that you supply; the SDK does no key management. signPass and verifyPass use WebCrypto only, so they are also in agent-work-layer/validate for browsers, Deno and Bun. To make a key pair in Node:

const { privateKey, publicKey } = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
console.log(JSON.stringify(await crypto.subtle.exportKey("jwk", privateKey)), JSON.stringify(await crypto.subtle.exportKey("jwk", publicKey)));

Over MCP, awl-desk serve --pass-key priv.json --issuer <url> issues passes, and awl-desk gateway --pass-keys pub.json enforces them; see the MCP package README.

Development

The repo is an npm workspace with two packages: the core in packages/core and the MCP server in packages/mcp. The spec, schemas and examples stay at the root; the core copies them in when it is packed.

npm install
npm test               # core and MCP tests (builds the core first)
npm run test:coverage
npm run typecheck
npm run test:package   # packs both packages, installs them in clean projects and uses them
npm run check          # all of the above; runs automatically before npm publish
npm run generate       # after editing schemas/*.json: refreshes packages/core/src/generated.ts

The Python package in python/ is separate from the npm workspace:

cd python
python -m venv .venv && .venv/bin/pip install -e ".[test]"
.venv/bin/pytest

Contributing

Issues, ideas and pull requests are welcome. See CONTRIBUTING.md and the roadmap. Spec changes start as a Spec proposal issue.

License

Apache-2.0. You can use, modify and ship it, including commercially. The license includes a patent grant.

版本与发布记录

agent-work-layer 共发布 15 个版本,首次发布于 2026/10/10;过去 12 个月发布了 15 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.9.1latest2026/11/03今天
0.9.02026/10/277 天前
0.8.02026/10/277 天前
0.7.12026/10/259 天前
0.7.02026/10/259 天前
0.6.02026/10/2211 天前

关于 agent-work-layer 的常见问题

agent-work-layer 是什么?

人工智能代理的工作指令和工作报告:一个类型SDK,验证器和生命周期桌面。MCP服务器在agent-work-layer-mcp中。

如何安装 agent-work-layer?

在项目目录运行 npm install agent-work-layer,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 awl,可以用 npx awl 直接运行。

agent-work-layer 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

agent-work-layer 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

agent-work-layer 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20.11。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

agent-work-layer 可以商用吗?

agent-work-layer 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

agent-work-layer 还在维护吗?

最新版本 0.9.1 发布于 2026/11/03(今天);至今共发布 15 个版本,其中过去 12 个月发布了 15 个;首次发布于 2026/10/10。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。