openclaw-zero-agency 0.2.4 → 1.0.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/README.md CHANGED
@@ -1,12 +1,16 @@
1
1
  # openclaw-zero-agency
2
2
 
3
- Capability routing and a receipt gate for OpenClaw fleets.
3
+ Capability routing for OpenClaw agents.
4
4
 
5
5
  **Packs inform judgement; they never confer authority.**
6
6
 
7
- - `zero_agency_route` — match a task to a capability pack, issue a receipt
8
- - `zero_agency_status` — registry version, capability count, gate mode
9
- - `before_tool_call` gate — consequential tools require a receipt
7
+ ## What this does
8
+
9
+ - `zero_agency_route` — match a task to a distilled capability pack and return the pack path
10
+ - `zero_agency_status` — registry state: capability count, token total
11
+ - Routing telemetry — one JSONL line per decision, for measuring routing quality
12
+
13
+ It does **not** enforce tool permissions. Enforcement belongs to OpenClaw's tool policy and exec approval configuration, which are evaluated by the platform, not by a plugin.
10
14
 
11
15
  ## Install
12
16
 
@@ -23,9 +27,7 @@ openclaw plugins install openclaw-zero-agency
23
27
  "zero-agency": {
24
28
  "config": {
25
29
  "registryPath": "/opt/zero-agency/registry/capabilities.json",
26
- "telemetryPath": "/var/log/zero-agency/events.jsonl",
27
- "gateMode": "advisory",
28
- "consequentialTools": ["exec", "apply_patch"]
30
+ "telemetryPath": "/var/log/zero-agency/events.jsonl"
29
31
  }
30
32
  }
31
33
  }
@@ -33,6 +35,16 @@ openclaw plugins install openclaw-zero-agency
33
35
  }
34
36
  ```
35
37
 
36
- `gateMode`: `off` | `advisory` (log only) | `enforce` (require approval, fail-closed).
38
+ `registryPath` is required and must point at a compiled `capabilities.json`.
39
+
40
+ ## The receipt gate is experimental and off by default
41
+
42
+ `gateMode` exists (`off` | `advisory` | `enforce`) but **`enforce` does not work in the current plugin host**. The `before_tool_call` hook and a tool's `execute` run in separate contexts that share neither memory, run id, nor filesystem — verified across four releases. In `enforce`, every gated call raises an approval prompt because no receipt can ever match.
43
+
44
+ Leave it `off`. The routing layer is independent of it and is fully functional.
45
+
46
+ ## Provenance
47
+
48
+ Packs are distilled from pinned upstream sources. Every capability records its source files and commit SHA in frontmatter; the registry is compiled, never hand-edited.
37
49
 
38
50
  Requires `openclaw >= 2026.10.1`.
package/dist/index.js CHANGED
@@ -8,7 +8,7 @@
8
8
  import { Type } from "typebox";
9
9
  import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
10
10
  import { readFile, appendFile, mkdir } from "node:fs/promises";
11
- import { readFileSync, writeFileSync, renameSync, mkdirSync } from "node:fs";
11
+ import { rename, writeFile } from "node:fs/promises";
12
12
  import { statSync } from "node:fs";
13
13
  import { createHash, randomUUID } from "node:crypto";
14
14
  import { dirname } from "node:path";
@@ -26,17 +26,17 @@ const cache = new Map();
26
26
  // Receipts are shared through a file, not memory: the tool and the hook run in
27
27
  // different execution contexts inside the plugin host, so a Map is not shared.
28
28
  let receiptPath = "/tmp/zero-agency-receipts.json";
29
- function readReceipts() {
29
+ async function readReceipts() {
30
30
  try {
31
- return JSON.parse(readFileSync(receiptPath, "utf8"));
31
+ return JSON.parse(await readFile(receiptPath, "utf8"));
32
32
  }
33
33
  catch {
34
34
  return {};
35
35
  }
36
36
  }
37
- function writeReceipt(runId, rec) {
37
+ async function writeReceipt(runId, rec) {
38
38
  const now = Date.now();
39
- const all = readReceipts();
39
+ const all = await readReceipts();
40
40
  for (const k of Object.keys(all)) {
41
41
  const v = all[k];
42
42
  if (!v || v.expires < now)
@@ -46,15 +46,18 @@ function writeReceipt(runId, rec) {
46
46
  if (runId)
47
47
  all[runId] = rec;
48
48
  try {
49
- mkdirSync(dirname(receiptPath), { recursive: true });
49
+ await mkdir(dirname(receiptPath), { recursive: true });
50
50
  const tmp = `${receiptPath}.tmp`;
51
- writeFileSync(tmp, JSON.stringify(all));
52
- renameSync(tmp, receiptPath);
51
+ await writeFile(tmp, JSON.stringify(all));
52
+ await rename(tmp, receiptPath);
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
53
57
  }
54
- catch { /* a missing receipt fails safe */ }
55
58
  }
56
- function findReceipt(runId) {
57
- const all = readReceipts();
59
+ async function findReceipt(runId) {
60
+ const all = await readReceipts();
58
61
  const now = Date.now();
59
62
  const r = (runId ? all[runId] : undefined) ?? all.global;
60
63
  return r && r.expires > now ? r : undefined;
@@ -117,7 +120,7 @@ export default definePluginEntry({
117
120
  const ttl = cfg.receiptTtlMs ?? 900000;
118
121
  api.registerTool({
119
122
  name: "zero_agency_route",
120
- description: "Find the capability pack governing a domain task. Call before acting on security review, release verification or incident triage. Issues a receipt the gate accepts.",
123
+ description: "Find the capability pack governing a domain task. Call before acting on security review, release verification or incident triage. Returns the governing pack and logs the routing decision.",
121
124
  parameters: Type.Object({
122
125
  task: Type.String({ description: "The task or question, in natural language." }),
123
126
  limit: Type.Optional(Type.Integer({ minimum: 1, maximum: 5 })),
@@ -148,7 +151,9 @@ export default definePluginEntry({
148
151
  if (candidates.length) {
149
152
  const runId = ctx?.runId ?? ctx?.sessionId ?? null;
150
153
  receipt = createHash("sha256").update(`${runId ?? "global"}:${candidates[0].id}:${Date.now()}:${randomUUID()}`).digest("hex").slice(0, 16);
151
- writeReceipt(runId, { id: receipt, pack: candidates[0].pack, at: Date.now(), expires: Date.now() + ttl });
154
+ const wrote = await writeReceipt(runId, { id: receipt, pack: candidates[0].pack, at: Date.now(), expires: Date.now() + ttl });
155
+ if (!wrote)
156
+ await telemetry(telemetryPath, { kind: "receipt-write-failed", path: receiptPath, receipt });
152
157
  }
153
158
  await telemetry(telemetryPath, { kind: "route", agent: ctx?.agentId ?? null, session: ctx?.sessionKey ?? null, query: params.task.slice(0, 200), selected: candidates[0]?.id ?? null, candidates: candidates.length, receipt });
154
159
  return { content: [{ type: "text", text: JSON.stringify({ candidates, receipt, note }) }], details: { candidates, receipt, note } };
@@ -175,7 +180,7 @@ export default definePluginEntry({
175
180
  return undefined;
176
181
  const runId = event?.runId ?? ctx?.runId ?? "unknown-run";
177
182
  prune();
178
- const r = findReceipt(runId);
183
+ const r = await findReceipt(runId);
179
184
  if (r) {
180
185
  await telemetry(telemetryPath, { kind: "gate", decision: "receipt-ok", tool: event?.toolName, runId, receipt: r.id });
181
186
  return undefined;
@@ -2,18 +2,55 @@
2
2
  "id": "zero-agency",
3
3
  "name": "Zero Agency",
4
4
  "description": "Capability routing with a receipt gate. Packs inform judgement; they never confer authority.",
5
- "activation": { "onStartup": true },
6
- "contracts": { "tools": ["zero_agency_route", "zero_agency_status"] },
5
+ "activation": {
6
+ "onStartup": true
7
+ },
8
+ "contracts": {
9
+ "tools": [
10
+ "zero_agency_route",
11
+ "zero_agency_status"
12
+ ]
13
+ },
7
14
  "configSchema": {
8
15
  "type": "object",
9
16
  "additionalProperties": false,
10
- "required": ["registryPath"],
17
+ "required": [
18
+ "registryPath"
19
+ ],
11
20
  "properties": {
12
- "registryPath": { "type": "string", "description": "Absolute path to capabilities.json (read-only)." },
13
- "telemetryPath": { "type": "string", "description": "JSONL telemetry path." },
14
- "receiptTtlMs": { "type": "integer", "minimum": 1000, "default": 900000 },
15
- "gateMode": { "type": "string", "enum": ["off", "advisory", "enforce"], "default": "advisory" },
16
- "consequentialTools": { "type": "array", "items": { "type": "string" }, "default": ["exec", "apply_patch"] }
21
+ "registryPath": {
22
+ "type": "string",
23
+ "description": "Absolute path to capabilities.json (read-only)."
24
+ },
25
+ "telemetryPath": {
26
+ "type": "string",
27
+ "description": "JSONL telemetry path."
28
+ },
29
+ "receiptTtlMs": {
30
+ "type": "integer",
31
+ "minimum": 1000,
32
+ "default": 900000
33
+ },
34
+ "gateMode": {
35
+ "type": "string",
36
+ "enum": [
37
+ "off",
38
+ "advisory",
39
+ "enforce"
40
+ ],
41
+ "default": "off",
42
+ "description": "Receipt gate mode. EXPERIMENTAL: cross-context receipt correlation does not work in the current plugin host, so keep this off."
43
+ },
44
+ "consequentialTools": {
45
+ "type": "array",
46
+ "items": {
47
+ "type": "string"
48
+ },
49
+ "default": [
50
+ "exec",
51
+ "apply_patch"
52
+ ]
53
+ }
17
54
  }
18
55
  }
19
56
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "openclaw-zero-agency",
3
- "version": "0.2.4",
4
- "description": "Zero Agency: capability packs, programmatic routing and a receipt gate for OpenClaw. Packs inform judgement; they never confer authority.",
3
+ "version": "1.0.0",
4
+ "description": "Capability routing for OpenClaw agents: distilled packs, a compiled registry, and routing telemetry. Packs inform judgement; they never confer authority.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "license": "MIT",