@enderfga/claw-orchestrator 5.1.0 → 6.0.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.
Files changed (152) hide show
  1. package/README.md +26 -26
  2. package/dist/bin/cli.js +107 -1
  3. package/dist/bin/cli.js.map +1 -1
  4. package/dist/src/acp-server.d.ts +5 -5
  5. package/dist/src/acp-server.js +3 -3
  6. package/dist/src/acp-server.js.map +1 -1
  7. package/dist/src/autoloop/dispatcher.d.ts +22 -0
  8. package/dist/src/autoloop/dispatcher.js +71 -13
  9. package/dist/src/autoloop/dispatcher.js.map +1 -1
  10. package/dist/src/autoloop/messages.d.ts +10 -0
  11. package/dist/src/autoloop/messages.js.map +1 -1
  12. package/dist/src/autoloop/runner.js +6 -0
  13. package/dist/src/autoloop/runner.js.map +1 -1
  14. package/dist/src/constants.d.ts +0 -6
  15. package/dist/src/constants.js +0 -6
  16. package/dist/src/constants.js.map +1 -1
  17. package/dist/src/council.d.ts +15 -0
  18. package/dist/src/council.js +48 -35
  19. package/dist/src/council.js.map +1 -1
  20. package/dist/src/dashboard/index.html +191 -6
  21. package/dist/src/embedded-server.js +132 -9
  22. package/dist/src/embedded-server.js.map +1 -1
  23. package/dist/src/fanout.d.ts +30 -1
  24. package/dist/src/fanout.js +32 -3
  25. package/dist/src/fanout.js.map +1 -1
  26. package/dist/src/index.js +359 -4
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/kernel/agent-step.d.ts +59 -0
  29. package/dist/src/kernel/agent-step.js +100 -0
  30. package/dist/src/kernel/agent-step.js.map +1 -0
  31. package/dist/src/kernel/conditions.d.ts +11 -0
  32. package/dist/src/kernel/conditions.js +24 -0
  33. package/dist/src/kernel/conditions.js.map +1 -0
  34. package/dist/src/kernel/engine.d.ts +319 -0
  35. package/dist/src/kernel/engine.js +1047 -0
  36. package/dist/src/kernel/engine.js.map +1 -0
  37. package/dist/src/kernel/exec.d.ts +43 -0
  38. package/dist/src/kernel/exec.js +112 -0
  39. package/dist/src/kernel/exec.js.map +1 -0
  40. package/dist/src/kernel/file-lock.d.ts +50 -0
  41. package/dist/src/kernel/file-lock.js +135 -0
  42. package/dist/src/kernel/file-lock.js.map +1 -0
  43. package/dist/src/kernel/nodes/agent.d.ts +4 -0
  44. package/dist/src/kernel/nodes/agent.js +35 -0
  45. package/dist/src/kernel/nodes/agent.js.map +1 -0
  46. package/dist/src/kernel/nodes/autoloop.d.ts +78 -0
  47. package/dist/src/kernel/nodes/autoloop.js +75 -0
  48. package/dist/src/kernel/nodes/autoloop.js.map +1 -0
  49. package/dist/src/kernel/nodes/council.d.ts +12 -0
  50. package/dist/src/kernel/nodes/council.js +88 -0
  51. package/dist/src/kernel/nodes/council.js.map +1 -0
  52. package/dist/src/kernel/nodes/fanout.d.ts +11 -0
  53. package/dist/src/kernel/nodes/fanout.js +63 -0
  54. package/dist/src/kernel/nodes/fanout.js.map +1 -0
  55. package/dist/src/kernel/nodes/human-gate.d.ts +4 -0
  56. package/dist/src/kernel/nodes/human-gate.js +7 -0
  57. package/dist/src/kernel/nodes/human-gate.js.map +1 -0
  58. package/dist/src/kernel/nodes/index.d.ts +12 -0
  59. package/dist/src/kernel/nodes/index.js +21 -0
  60. package/dist/src/kernel/nodes/index.js.map +1 -0
  61. package/dist/src/kernel/nodes/router.d.ts +4 -0
  62. package/dist/src/kernel/nodes/router.js +12 -0
  63. package/dist/src/kernel/nodes/router.js.map +1 -0
  64. package/dist/src/kernel/nodes/subflow.d.ts +13 -0
  65. package/dist/src/kernel/nodes/subflow.js +38 -0
  66. package/dist/src/kernel/nodes/subflow.js.map +1 -0
  67. package/dist/src/kernel/nodes/ultraapp.d.ts +60 -0
  68. package/dist/src/kernel/nodes/ultraapp.js +62 -0
  69. package/dist/src/kernel/nodes/ultraapp.js.map +1 -0
  70. package/dist/src/kernel/nodes/verifier.d.ts +14 -0
  71. package/dist/src/kernel/nodes/verifier.js +84 -0
  72. package/dist/src/kernel/nodes/verifier.js.map +1 -0
  73. package/dist/src/kernel/projections.d.ts +42 -0
  74. package/dist/src/kernel/projections.js +133 -0
  75. package/dist/src/kernel/projections.js.map +1 -0
  76. package/dist/src/kernel/repo.d.ts +13 -0
  77. package/dist/src/kernel/repo.js +64 -0
  78. package/dist/src/kernel/repo.js.map +1 -0
  79. package/dist/src/kernel/secrets.d.ts +25 -0
  80. package/dist/src/kernel/secrets.js +48 -0
  81. package/dist/src/kernel/secrets.js.map +1 -0
  82. package/dist/src/kernel/store.d.ts +225 -0
  83. package/dist/src/kernel/store.js +838 -0
  84. package/dist/src/kernel/store.js.map +1 -0
  85. package/dist/src/kernel/templates/index.d.ts +140 -0
  86. package/dist/src/kernel/templates/index.js +266 -0
  87. package/dist/src/kernel/templates/index.js.map +1 -0
  88. package/dist/src/kernel/types.d.ts +326 -0
  89. package/dist/src/kernel/types.js +19 -0
  90. package/dist/src/kernel/types.js.map +1 -0
  91. package/dist/src/models.d.ts +7 -0
  92. package/dist/src/models.js +43 -14
  93. package/dist/src/models.js.map +1 -1
  94. package/dist/src/persistent-custom-session.js +8 -3
  95. package/dist/src/persistent-custom-session.js.map +1 -1
  96. package/dist/src/run-ledger.d.ts +57 -3
  97. package/dist/src/run-ledger.js +45 -2
  98. package/dist/src/run-ledger.js.map +1 -1
  99. package/dist/src/session-manager.d.ts +176 -129
  100. package/dist/src/session-manager.js +652 -603
  101. package/dist/src/session-manager.js.map +1 -1
  102. package/dist/src/types.d.ts +33 -3
  103. package/dist/src/ultraapp/build.d.ts +117 -3
  104. package/dist/src/ultraapp/build.js +319 -3
  105. package/dist/src/ultraapp/build.js.map +1 -1
  106. package/dist/src/ultraapp/contract.d.ts +52 -0
  107. package/dist/src/ultraapp/contract.js +83 -0
  108. package/dist/src/ultraapp/contract.js.map +1 -0
  109. package/dist/src/ultraapp/conventions.js +9 -2
  110. package/dist/src/ultraapp/conventions.js.map +1 -1
  111. package/dist/src/ultraapp/fix-on-failure.d.ts +21 -2
  112. package/dist/src/ultraapp/fix-on-failure.js +46 -62
  113. package/dist/src/ultraapp/fix-on-failure.js.map +1 -1
  114. package/dist/src/ultraapp/manager.d.ts +107 -2
  115. package/dist/src/ultraapp/manager.js +305 -86
  116. package/dist/src/ultraapp/manager.js.map +1 -1
  117. package/dist/src/verify/baseline.d.ts +73 -0
  118. package/dist/src/verify/baseline.js +186 -0
  119. package/dist/src/verify/baseline.js.map +1 -0
  120. package/dist/src/verify/contract.d.ts +116 -0
  121. package/dist/src/verify/contract.js +142 -0
  122. package/dist/src/verify/contract.js.map +1 -0
  123. package/dist/src/verify/evidence.d.ts +61 -0
  124. package/dist/src/verify/evidence.js +133 -0
  125. package/dist/src/verify/evidence.js.map +1 -0
  126. package/dist/src/verify/runner.d.ts +63 -0
  127. package/dist/src/verify/runner.js +317 -0
  128. package/dist/src/verify/runner.js.map +1 -0
  129. package/openclaw.plugin.json +38 -1
  130. package/package.json +2 -2
  131. package/skills/SKILL.md +120 -79
  132. package/skills/references/acp.md +17 -17
  133. package/skills/references/autoloop.md +139 -65
  134. package/skills/references/claude-cli-tracking.md +4 -4
  135. package/skills/references/cli.md +101 -59
  136. package/skills/references/council.md +109 -37
  137. package/skills/references/dashboard.md +34 -6
  138. package/skills/references/getting-started.md +13 -13
  139. package/skills/references/inbox.md +4 -4
  140. package/skills/references/mcp.md +39 -34
  141. package/skills/references/multi-engine.md +51 -47
  142. package/skills/references/observability.md +115 -28
  143. package/skills/references/openai-compat.md +39 -39
  144. package/skills/references/sessions.md +43 -25
  145. package/skills/references/tools.md +402 -309
  146. package/skills/references/ultra.md +45 -45
  147. package/skills/references/ultraapp.md +126 -50
  148. package/skills/references/verification.md +187 -0
  149. package/skills/references/workflow.md +362 -0
  150. package/dist/src/ultraapp/fix-on-failure-session.d.ts +0 -23
  151. package/dist/src/ultraapp/fix-on-failure-session.js +0 -51
  152. package/dist/src/ultraapp/fix-on-failure-session.js.map +0 -1
@@ -39,6 +39,7 @@ Default engine. Long-running subprocess with streaming JSON I/O. Tested with Cla
39
39
  - Fork subagent (`forkSubagent`), tool search (`enableToolSearch`), OpenTelemetry logging toggles (`otelLogUserPrompts`, `otelLogRawApiBodies`), `xhigh` effort tier (Opus 4.7), and `stats.pluginErrors` capture — see [CLI 2.1.121 options in SKILL.md](../SKILL.md) and [tools.md](./tools.md)
40
40
 
41
41
  > **Behavior changes from upstream Claude CLI 2.1.121** (worth knowing if you set permission rules):
42
+ >
42
43
  > - `--agent` / `--print` now enforce agent frontmatter `permissionMode`, `tools`, `disallowedTools` (was advisory). Affects `council` agent personas.
43
44
  > - `Bash(find:*)` permission rule no longer auto-approves `find -exec` or `find -delete`. Add explicit rules if you depend on these.
44
45
  > - `--dangerously-skip-permissions` also skips prompts for `.claude/skills/` directory. Treat with care.
@@ -47,7 +48,7 @@ Default engine. Long-running subprocess with streaming JSON I/O. Tested with Cla
47
48
  ```typescript
48
49
  await manager.startSession({
49
50
  name: 'claude-task',
50
- engine: 'claude', // default, can omit
51
+ engine: 'claude', // default, can omit
51
52
  model: 'opus',
52
53
  cwd: '/project',
53
54
  });
@@ -140,6 +141,7 @@ in print mode. Verified against `agy` **1.1.13**.
140
141
  `gemini-3.1-pro has no "medium" effort (available: low, high)`. The adapter
141
142
  passes the requested effort through rather than substituting a tier the caller
142
143
  did not ask for; run `agy models` to see the tiers a slug actually exposes.
144
+
143
145
  - Permission modes: `bypassPermissions` → `--dangerously-skip-permissions`,
144
146
  `default` → `--sandbox` (terminal-restricted), and
145
147
  `sandboxMode: 'read-only'` → `--mode plan` (takes precedence). Other modes
@@ -325,12 +327,12 @@ is not the answer on every engine. See "Stats & Monitoring" in `sessions.md`.
325
327
 
326
328
  Team tools (`team_list`, `team_send`) operate on the same virtual-team layer for **every** engine: the "team" is the set of all active sessions managed by SessionManager.
327
329
 
328
- | Engine | `team_list` | `team_send` |
329
- |--------|------------|-------------|
330
- | Claude | Lists other active SessionManager sessions | Routes via cross-session inbox |
331
- | Codex | Lists other active SessionManager sessions | Routes via cross-session inbox |
330
+ | Engine | `team_list` | `team_send` |
331
+ | ----------- | ------------------------------------------ | ------------------------------ |
332
+ | Claude | Lists other active SessionManager sessions | Routes via cross-session inbox |
333
+ | Codex | Lists other active SessionManager sessions | Routes via cross-session inbox |
332
334
  | Antigravity | Lists other active SessionManager sessions | Routes via cross-session inbox |
333
- | Cursor | Lists other active SessionManager sessions | Routes via cross-session inbox |
335
+ | Cursor | Lists other active SessionManager sessions | Routes via cross-session inbox |
334
336
 
335
337
  Messages are delivered via the inbox system — idle sessions receive immediately, busy sessions queue for later delivery.
336
338
 
@@ -349,12 +351,13 @@ If OpenClaw gateway is running, everything is automatic:
349
351
  await manager.startSession({
350
352
  name: 'task',
351
353
  engine: 'claude',
352
- model: 'openclaw', // gateway routes to your configured model
354
+ model: 'openclaw', // gateway routes to your configured model
353
355
  cwd: '/project',
354
356
  });
355
357
  ```
356
358
 
357
359
  What happens behind the scenes:
360
+
358
361
  1. Plugin reads `~/.openclaw/openclaw.json` for gateway port + auth
359
362
  2. Starts a local proxy server (random port, auto-managed)
360
363
  3. Claude Code CLI sends Anthropic-format requests → proxy converts to OpenAI → gateway → any model
@@ -363,12 +366,12 @@ What happens behind the scenes:
363
366
 
364
367
  Override with environment variables if needed:
365
368
 
366
- | Variable | Default | Description |
367
- |----------|---------|-------------|
368
- | `GATEWAY_URL` | Auto-detected from openclaw.json | Gateway endpoint (e.g. `http://127.0.0.1:18789/v1`) |
369
- | `GATEWAY_KEY` | Auto-detected from openclaw.json | Gateway auth password/token |
370
- | `GEMINI_API_KEY` | - | Direct Gemini API access (bypasses gateway) |
371
- | `OPENAI_API_KEY` | - | Direct OpenAI API access (bypasses gateway) |
369
+ | Variable | Default | Description |
370
+ | ---------------- | -------------------------------- | --------------------------------------------------- |
371
+ | `GATEWAY_URL` | Auto-detected from openclaw.json | Gateway endpoint (e.g. `http://127.0.0.1:18789/v1`) |
372
+ | `GATEWAY_KEY` | Auto-detected from openclaw.json | Gateway auth password/token |
373
+ | `GEMINI_API_KEY` | - | Direct Gemini API access (bypasses gateway) |
374
+ | `OPENAI_API_KEY` | - | Direct OpenAI API access (bypasses gateway) |
372
375
 
373
376
  ### Architecture
374
377
 
@@ -384,46 +387,47 @@ Claude Code CLI (Anthropic format)
384
387
  Integrate **any** coding agent CLI without writing engine-specific code. You provide a `CustomEngineConfig` that maps your CLI's flags to OpenClaw session concepts.
385
388
 
386
389
  Two protocol modes:
390
+
387
391
  - **Persistent** (`persistent: true`) — long-running subprocess with stream-json I/O over stdin/stdout (like Claude Code)
388
392
  - **One-shot** (`persistent: false`, default) — new process spawned per `send()` (like Codex/Antigravity)
389
393
 
390
394
  ### CustomEngineConfig
391
395
 
392
- | Field | Type | Required | Description |
393
- |-------|------|----------|-------------|
394
- | `name` | string | yes | Display name (used in logs, session IDs) |
395
- | `bin` | string | yes | Binary path or command name |
396
- | `binEnv` | string | | Env var name that overrides `bin` at runtime |
397
- | `persistent` | boolean | | `true` = persistent subprocess, `false` = one-shot (default) |
398
- | `args` | object | yes | CLI flag mappings (see below) |
399
- | `permissionModes` | object | | Maps OpenClaw mode names to CLI-specific values |
400
- | `pricing` | object | | `{ input, output, cached? }` per 1M tokens |
401
- | `contextWindow` | number | | Context window size (default: 200,000) |
402
- | `env` | object | | Extra environment variables for the CLI process |
403
- | `sanitizePatterns` | string[] | | Regex patterns to redact from stderr |
396
+ | Field | Type | Required | Description |
397
+ | ------------------ | -------- | -------- | ------------------------------------------------------------ |
398
+ | `name` | string | yes | Display name (used in logs, session IDs) |
399
+ | `bin` | string | yes | Binary path or command name |
400
+ | `binEnv` | string | | Env var name that overrides `bin` at runtime |
401
+ | `persistent` | boolean | | `true` = persistent subprocess, `false` = one-shot (default) |
402
+ | `args` | object | yes | CLI flag mappings (see below) |
403
+ | `permissionModes` | object | | Maps OpenClaw mode names to CLI-specific values |
404
+ | `pricing` | object | | `{ input, output, cached? }` per 1M tokens |
405
+ | `contextWindow` | number | | Context window size (default: 200,000) |
406
+ | `env` | object | | Extra environment variables for the CLI process |
407
+ | `sanitizePatterns` | string[] | | Regex patterns to redact from stderr |
404
408
 
405
409
  ### args field
406
410
 
407
- | Key | Example | Description |
408
- |-----|---------|-------------|
409
- | `print` | `"-p"` | Non-interactive/print mode flag |
410
- | `outputFormat` | `"--output-format"` | Output format flag |
411
- | `outputFormatValue` | `"stream-json"` | Value for stream-json output |
412
- | `inputFormat` | `"--input-format"` | Input format flag (persistent only) |
413
- | `inputFormatValue` | `"stream-json"` | Value for stream-json input |
414
- | `skipPermissions` | `"-y"` | Skip all permissions flag |
415
- | `permissionMode` | `"--permission-mode"` | Permission mode flag |
416
- | `model` | `"--model"` | Model selection flag |
417
- | `systemPrompt` | `"--system-prompt"` | System prompt override flag |
418
- | `appendSystemPrompt` | `"--append-system-prompt"` | Append system prompt flag |
419
- | `maxTurns` | `"--max-turns"` | Max agent turns flag |
420
- | `resume` | `"--resume"` | Session resume flag (persistent only) |
421
- | `verbose` | `"--verbose"` | Verbose output flag |
422
- | `replayUserMessages` | `"--replay-user-messages"` | Replay user messages (persistent only) |
411
+ | Key | Example | Description |
412
+ | ------------------------ | ------------------------------ | ------------------------------------------ |
413
+ | `print` | `"-p"` | Non-interactive/print mode flag |
414
+ | `outputFormat` | `"--output-format"` | Output format flag |
415
+ | `outputFormatValue` | `"stream-json"` | Value for stream-json output |
416
+ | `inputFormat` | `"--input-format"` | Input format flag (persistent only) |
417
+ | `inputFormatValue` | `"stream-json"` | Value for stream-json input |
418
+ | `skipPermissions` | `"-y"` | Skip all permissions flag |
419
+ | `permissionMode` | `"--permission-mode"` | Permission mode flag |
420
+ | `model` | `"--model"` | Model selection flag |
421
+ | `systemPrompt` | `"--system-prompt"` | System prompt override flag |
422
+ | `appendSystemPrompt` | `"--append-system-prompt"` | Append system prompt flag |
423
+ | `maxTurns` | `"--max-turns"` | Max agent turns flag |
424
+ | `resume` | `"--resume"` | Session resume flag (persistent only) |
425
+ | `verbose` | `"--verbose"` | Verbose output flag |
426
+ | `replayUserMessages` | `"--replay-user-messages"` | Replay user messages (persistent only) |
423
427
  | `includePartialMessages` | `"--include-partial-messages"` | Include partial messages (persistent only) |
424
- | `effort` | `"--effort"` | Effort level flag |
425
- | `workspace` | `"--workspace"` | Workspace/cwd flag (one-shot only) |
426
- | `extra` | `["--trust"]` | Additional static arguments |
428
+ | `effort` | `"--effort"` | Effort level flag |
429
+ | `workspace` | `"--workspace"` | Workspace/cwd flag (one-shot only) |
430
+ | `extra` | `["--trust"]` | Additional static arguments |
427
431
 
428
432
  ### Example: Persistent mode (Claude Code-compatible CLI)
429
433
 
@@ -471,7 +475,7 @@ await manager.startSession({
471
475
  customEngine: {
472
476
  name: 'simple-agent',
473
477
  bin: '/usr/local/bin/simple-agent',
474
- persistent: false, // default
478
+ persistent: false, // default
475
479
  args: {
476
480
  print: '-p',
477
481
  outputFormat: '--output-format',
@@ -505,11 +509,11 @@ await manager.startSession({
505
509
  dangerouslySkipPermissions: true,
506
510
  customEngine: {
507
511
  name: 'antigravity',
508
- bin: 'agy', // install: curl -fsSL https://antigravity.google/cli/install.sh | bash
512
+ bin: 'agy', // install: curl -fsSL https://antigravity.google/cli/install.sh | bash
509
513
  binEnv: 'AGY_BIN',
510
514
  persistent: false,
511
515
  args: {
512
- print: '-p', // single-prompt headless mode
516
+ print: '-p', // single-prompt headless mode
513
517
  skipPermissions: '--dangerously-skip-permissions',
514
518
  workspace: '--add-dir',
515
519
  // NOTE: agy 1.0.2 has NO --output-format flag — output is plain text only.
@@ -30,22 +30,22 @@ every engine passes through.
30
30
 
31
31
  ### Row schema
32
32
 
33
- | Field | Meaning |
34
- |---|---|
35
- | `ts` | ISO timestamp of turn completion |
36
- | `session` | SessionManager session name |
37
- | `engine` | `claude` / `codex` / `codex-app` / `grok` / `opencode` / `agy` / `custom` |
38
- | `model` | Configured model, or the engine's own reported model when none was set |
39
- | `cwd` | Working directory the turn ran in |
40
- | `turn` | 1-based turn index within the session |
41
- | `tokensIn` / `tokensOut` / `cachedTokens` | **Per-turn deltas**, not session totals |
42
- | `costUsd` | Per-turn delta in USD |
43
- | `tokensEstimated` | `true` when the counts came from `estimateTokens()` (see below) |
44
- | `durationMs` | Wall-clock for the turn |
45
- | `toolCalls` / `toolErrors` | Per-turn deltas |
46
- | `ok` | `false` for a turn that threw, or that the session's own `turnsSucceeded` counter did not count (see `sessions.md`). Falls back to "nothing was thrown" when the counter cannot be read |
47
- | `error` | Failure text, truncated to 500 chars. Absent when the turn resolved but the engine did not count it as succeeded (an interrupted or non-SUCCESS turn), so a failed row does not always carry one |
48
- | `parent` | council id / fanout id / autoloop run id, when the turn belongs to one |
33
+ | Field | Meaning |
34
+ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
35
+ | `ts` | ISO timestamp of turn completion |
36
+ | `session` | SessionManager session name |
37
+ | `engine` | `claude` / `codex` / `codex-app` / `grok` / `opencode` / `agy` / `custom` |
38
+ | `model` | Configured model, or the engine's own reported model when none was set |
39
+ | `cwd` | Working directory the turn ran in |
40
+ | `turn` | 1-based turn index within the session |
41
+ | `tokensIn` / `tokensOut` / `cachedTokens` | **Per-turn deltas**, not session totals |
42
+ | `costUsd` | Per-turn delta in USD — token count × the registry rate. On a flat-rate plan (a subscription seat) nobody was billed this: set `pricingOverrides` in the plugin config to zero the model out (`{"gpt-5.5": {"input": 0, "output": 0, "cached": 0}}`). Read [Zeroing a model's pricing](#zeroing-a-models-pricing) first — it disables `maxBudgetUsd` for that model, except on `grok`, which prices itself |
43
+ | `tokensEstimated` | `true` when the counts came from `estimateTokens()` (see below) |
44
+ | `durationMs` | Wall-clock for the turn |
45
+ | `toolCalls` / `toolErrors` | Per-turn deltas |
46
+ | `ok` | `false` for a turn that threw, or that the session's own `turnsSucceeded` counter did not count (see `sessions.md`). Falls back to "nothing was thrown" when the counter cannot be read |
47
+ | `error` | Failure text, truncated to 500 chars. Absent when the turn resolved but the engine did not count it as succeeded (an interrupted or non-SUCCESS turn), so a failed row does not always carry one |
48
+ | `parent` | council id / fanout id / autoloop run id, when the turn belongs to one |
49
49
 
50
50
  Deltas rather than totals means summing a query window gives that window's spend
51
51
  without double-counting.
@@ -98,11 +98,38 @@ Notes:
98
98
  - The check is "has the cap been reached", not "would this turn exceed it" — a
99
99
  turn's cost is unknown until it finishes, so the last allowed turn can overshoot.
100
100
  Size the cap accordingly.
101
- - A cap of `0` or a negative number means *unset*, not *refuse everything*.
101
+ - A cap of `0` or a negative number means _unset_, not _refuse everything_.
102
102
  - Claude Code still receives `--max-budget-usd` as well: an in-CLI stop happens
103
103
  earlier and therefore costs less than an after-the-fact refusal.
104
104
  - `session_list` / `GET /session/list` expose `costUsd`, `budgetUsd` and
105
- `budgetExhausted` so a stalled session shows *why* it stopped taking turns.
105
+ `budgetExhausted` so a stalled session shows _why_ it stopped taking turns.
106
+
107
+ ### Zeroing a model's pricing
108
+
109
+ `pricingOverrides` zeroes the fiction that a subscription seat gets billed per token, and
110
+ the cost figures above stop reporting money nobody paid. It also **disables `maxBudgetUsd`
111
+ for that model**: the cap compares the session's accrued `getCost().totalUsd` against it,
112
+ and that number is pricing-derived, so at a rate of 0 it never reaches any cap. There is
113
+ no separate token or turn ceiling behind it.
114
+
115
+ - `engine: 'claude'` keeps the CLI's own `--max-budget-usd`, which the CLI accounts itself
116
+ and which a zeroed registry rate does not touch. `engine: 'grok'` is outside this for a
117
+ different reason: it writes the engine's own `total_cost_usd` into `_stats.costUsd` and
118
+ never consults the registry, so zeroing a model's pricing does not disable its cap.
119
+ For codex, cursor, agy, opencode and custom the runtime check is the only one.
120
+ - `maxTurns` (and `maxRounds` on a council) still bound the work, and they are the right
121
+ ceiling to reach for once a model prices at 0.
122
+ - In a council the cap cannot bite before an agent's second turn in any case: the first
123
+ enters with nothing accrued, and one `sendMessage` runs up to `maxTurnsPerAgent` turns
124
+ inside the CLI, so it can pass the whole cap on its own.
125
+ - `cursor` and `opencode` sessions started without a model price by their engine default,
126
+ which is `claude-sonnet-4-6` for both. Zeroing a Claude subscription therefore zeroes
127
+ those default sessions too, and their reported ids (`cursor-default`, `opencode-default`)
128
+ are display names that cannot themselves be overridden.
129
+
130
+ The override is read once, in the `SessionManager` constructor, from the plugin config.
131
+ `clawo serve`, `clawo-acp` and `clawo-mcp` do not pass one, so a ledger written by those
132
+ processes prices at registry rates whichever way this is set.
106
133
 
107
134
  ## Accuracy: which engines report real usage
108
135
 
@@ -111,16 +138,16 @@ Where the engine reports usage, those counts are the engine's own. Where it does
111
138
  not, the wrapper falls back to `estimateTokens()` (characters ÷ 4) and the row is
112
139
  flagged `tokensEstimated: true`; the CLI marks those costs with a trailing `~`.
113
140
 
114
- | Engine | Token counts |
115
- |---|---|
116
- | `claude` | Engine-reported |
117
- | `codex` | Engine-reported |
118
- | `codex-app` | Engine-reported |
119
- | `grok` | Engine-reported — and so is the **cost**: this engine reports `total_cost_usd`, which the wrapper passes through instead of pricing tokens from the registry, so registry drift cannot affect a grok row |
120
- | `cursor` (legacy) | Engine-reported when the stream carries `usage`, else estimated |
121
- | `opencode` | Engine-reported when the run JSON carries `tokens`, else estimated |
122
- | `agy` | Engine-reported when the result event carries usage, else estimated |
123
- | `custom` | Depends on the CLI; estimated when it emits no usage |
141
+ | Engine | Token counts |
142
+ | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
143
+ | `claude` | Engine-reported |
144
+ | `codex` | Engine-reported |
145
+ | `codex-app` | Engine-reported |
146
+ | `grok` | Engine-reported — and so is the **cost**: this engine reports `total_cost_usd`, which the wrapper passes through instead of pricing tokens from the registry, so registry drift cannot affect a grok row |
147
+ | `cursor` (legacy) | Engine-reported when the stream carries `usage`, else estimated |
148
+ | `opencode` | Engine-reported when the run JSON carries `tokens`, else estimated |
149
+ | `agy` | Engine-reported when the result event carries usage, else estimated |
150
+ | `custom` | Depends on the CLI; estimated when it emits no usage |
124
151
 
125
152
  So on an estimating engine the cap is best-effort. It will stop a runaway session;
126
153
  it is not an accounting guarantee, and it is not a substitute for the spend limits
@@ -130,3 +157,63 @@ Cost figures are also only as good as the pricing table: a model missing from
130
157
  `models.ts` prices at its family default, and subscription plans (Claude Max,
131
158
  ChatGPT Pro) bill nothing per token while the ledger still reports the API-rate
132
159
  equivalent. Read `costUsd` as "what this would cost at API rates".
160
+
161
+ ## `ok` vs `verified` (6.0.0)
162
+
163
+ A row now carries two different judgements, and conflating them is the mistake
164
+ this section exists to prevent.
165
+
166
+ - **`ok`** — the engine's own terminal verdict for that turn. Codex fails a turn
167
+ that emits `turn.failed` while exiting 0; gemini succeeds on exit 53. It is a
168
+ careful signal, but it is the engine talking about itself.
169
+ - **`verified`** — an acceptance contract ran against the work and every required
170
+ check passed. That is the runtime's own measurement.
171
+
172
+ Three states, not two:
173
+
174
+ | `verified` | Means | CLI column |
175
+ | ---------- | --------------------------------------------------- | ---------- |
176
+ | `true` | A contract ran and passed | `yes` |
177
+ | `false` | A contract ran and a required check failed | `NO` |
178
+ | absent | **No contract was declared. Nothing checked this.** | `—` |
179
+
180
+ Absent is not false. An unchecked run is not a failed one, and reading it as
181
+ either would make the ledger useless for the thing it is for.
182
+
183
+ ### Where the verdict comes from
184
+
185
+ `verified` is **not written at turn time**, deliberately. The turns that produce
186
+ the work all finish before the verifier that judges it, so stamping a verdict on
187
+ them as they are written would be inventing one. It is joined in at read time
188
+ from the run record via the row's `parent`, by `annotateVerdicts()`.
189
+
190
+ Two consequences worth knowing:
191
+
192
+ - A raw `runs/*.jsonl` line usually has no `verified` field. Read through
193
+ `clawo runs` / `GET /runs` / `getRunLedger()` to get the join.
194
+ - Filtering on `--verified` happens _after_ the join. Pushing the filter into the
195
+ ledger read would match on a field no row carries yet and return nothing.
196
+
197
+ ### Other new row fields
198
+
199
+ | Field | Source |
200
+ | -------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
201
+ | `evidenceId`, `contractId` | Joined from the run record |
202
+ | `nodeKind` | The kernel node the turn belonged to (`agent`, `council`, `verifier`, …) |
203
+ | `repoLang` | Detected from a manifest (`package.json`, `pyproject.toml`, `go.mod`, …). Never guessed — a wrong label would corrupt the comparison the field exists to enable |
204
+ | `taskKind` | Caller-declared only. Never inferred from the prompt |
205
+
206
+ All are optional and absent on rows written before 6.0.0. The reader already
207
+ skips unknown and missing keys, so old shards stay readable; nothing backfills
208
+ them, because we cannot know retroactively.
209
+
210
+ ```bash
211
+ clawo runs --since 7d --verified # only turns whose contract passed
212
+ clawo runs --since 7d --refuted # only turns whose contract failed
213
+ clawo runs --parent wf-abc123 # every turn of one workflow run
214
+ ```
215
+
216
+ ## Related
217
+
218
+ - [`verification.md`](./verification.md) — what a contract is and how a verdict is produced
219
+ - [`workflow.md`](./workflow.md) — where run records live
@@ -11,13 +11,13 @@ Both modes share the same wire protocol; the difference is how a "new conversati
11
11
 
12
12
  ## Endpoint
13
13
 
14
- | | |
15
- |---|---|
16
- | **URL** | `http://127.0.0.1:18796/v1/chat/completions` |
17
- | **Models endpoint** | `GET /v1/models` |
18
- | **Inspection endpoint** | `GET /v1/sessions` (lists active openai-compat sessions with caching stats) |
19
- | **Auth** | Bearer token via `Authorization: Bearer $OPENCLAW_SERVER_TOKEN` (set the env var to enable; otherwise no auth and the server is loopback-only) |
20
- | **Wire format** | OpenAI Chat Completions, both streaming (SSE) and non-streaming |
14
+ | | |
15
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | **URL** | `http://127.0.0.1:18796/v1/chat/completions` |
17
+ | **Models endpoint** | `GET /v1/models` |
18
+ | **Inspection endpoint** | `GET /v1/sessions` (lists active openai-compat sessions with caching stats) |
19
+ | **Auth** | Bearer token via `Authorization: Bearer $OPENCLAW_SERVER_TOKEN` (set the env var to enable; otherwise no auth and the server is loopback-only) |
20
+ | **Wire format** | OpenAI Chat Completions, both streaming (SSE) and non-streaming |
21
21
 
22
22
  ## Session keying
23
23
 
@@ -56,20 +56,20 @@ Without this flag, those frontends would silently continue the previous CLI sess
56
56
 
57
57
  The env var is read on every request, so ops can flip it via `launchctl setenv` (or equivalent) without restarting the server.
58
58
 
59
- | Mode | Best for | New-conversation signals |
60
- |---|---|---|
61
- | **Default** | OpenClaw main agent, cron jobs, subagents, scripted clients | `X-Session-Reset: 1` only |
59
+ | Mode | Best for | New-conversation signals |
60
+ | ----------------- | ----------------------------------------------------------- | --------------------------------------------------- |
61
+ | **Default** | OpenClaw main agent, cron jobs, subagents, scripted clients | `X-Session-Reset: 1` only |
62
62
  | **`HEURISTIC=1`** | ChatGPT-Next-Web, Open WebUI, LobeChat, data labeling tools | `X-Session-Reset: 1` **and** `[system, user]` shape |
63
63
 
64
64
  ## Status webhook
65
65
 
66
66
  When `OPENAI_COMPAT_STATUS_URL` is set (full HTTP URL), each chat completion sends best-effort `POST` requests with `Content-Type: application/json` and body:
67
67
 
68
- | Field | Type | Meaning |
69
- |---|---|---|
70
- | `state` | string | `thinking` (turn started), `working` (a tool is running), or `idle` (turn finished or stream closed). |
71
- | `activity` | string | Short human-readable line, e.g. `Processing request...`, `Reading: foo.ts`, `Running: npm test...`. |
72
- | `tool` | string \| null | Tool name when `state === working`, otherwise `null`. |
68
+ | Field | Type | Meaning |
69
+ | ---------- | -------------- | ----------------------------------------------------------------------------------------------------- |
70
+ | `state` | string | `thinking` (turn started), `working` (a tool is running), or `idle` (turn finished or stream closed). |
71
+ | `activity` | string | Short human-readable line, e.g. `Processing request...`, `Reading: foo.ts`, `Running: npm test...`. |
72
+ | `tool` | string \| null | Tool name when `state === working`, otherwise `null`. |
73
73
 
74
74
  Failures are ignored (no retries). Use this from a small local HTTP handler that forwards status into your webchat UI.
75
75
 
@@ -78,17 +78,17 @@ Failures are ignored (no retries). Use this from a small local HTTP handler that
78
78
  When the request carries `tools`, the schemas have to reach the CLI somehow. Which
79
79
  mechanism is used depends on whether the engine keeps the conversation itself.
80
80
 
81
- | Engine | Turn 1 | Later turns |
82
- |---|---|---|
83
- | `claude` | Schemas go into the session system prompt (`--system-prompt`) | Nothing injected — the system prompt persists |
84
- | `codex`, `codex-app`, `agy`, `opencode`, `grok` | Full schema block prepended to the message | A short reminder of the calling convention, no schemas — but only once the conversation id has been captured; until then the full block is sent again |
85
- | `gemini`, one-shot `custom` | Full schema block prepended to the message | Full schema block again — these have no resume surface, so nothing persists between sends |
81
+ | Engine | Turn 1 | Later turns |
82
+ | ----------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
83
+ | `claude` | Schemas go into the session system prompt (`--system-prompt`) | Nothing injected — the system prompt persists |
84
+ | `codex`, `codex-app`, `agy`, `opencode`, `grok` | Full schema block prepended to the message | A short reminder of the calling convention, no schemas — but only once the conversation id has been captured; until then the full block is sent again |
85
+ | `gemini`, one-shot `custom` | Full schema block prepended to the message | Full schema block again — these have no resume surface, so nothing persists between sends |
86
86
 
87
87
  The middle row is the one worth understanding. Those engines resume a conversation by id, so
88
88
  everything injected stays in the transcript. Re-sending the full block each turn
89
89
  grows the prompt without bound — a 54-tool block runs to roughly 17k tokens, so a
90
90
  handful of turns is enough to overflow the context window mid-loop and fail the
91
- run outright. Sending *nothing* on resume turns is not the answer either: the
91
+ run outright. Sending _nothing_ on resume turns is not the answer either: the
92
92
  block also carries the "emit a tool call, do not carry out the work yourself"
93
93
  framing, and without it the CLI starts doing the work directly.
94
94
 
@@ -116,16 +116,16 @@ does change mid-conversation.
116
116
 
117
117
  ## Environment variables
118
118
 
119
- | Variable | Default | Purpose |
120
- |---|---|---|
121
- | `OPENCLAW_SERVER_TOKEN` | (unset) | Bearer token for HTTP auth. Set to enable; written to `~/.openclaw/server-token` for the CLI. |
122
- | `OPENCLAW_RATE_LIMIT` | `300` | Max requests per IP per 60-second sliding window. |
123
- | `OPENCLAW_CORS_ORIGINS` | (loopback only) | Set to `*` to allow all origins (the `/v1/*` paths already do this). |
124
- | `OPENAI_COMPAT_NEW_CONVO_HEURISTIC` | (unset) | Set to `1` to enable webchat mode (see above). |
125
- | `OPENAI_COMPAT_TOOLS_PER_MESSAGE` | (unset) | Set to `1` to re-send the full tool schemas on every turn (see [Tool definitions](#tool-definitions-and-where-they-live)). Needed only when the tool set changes mid-conversation; costs per-turn prompt growth. |
126
- | `OPENAI_COMPAT_STATUS_URL` | (unset) | If set, the bridge POSTs JSON status updates to this URL (fire-and-forget, 2s timeout). See [Status webhook](#status-webhook). |
127
- | `OPENCLAW_SERVE_MAX_SESSIONS` | `32` | Max concurrent OpenAI-compat sessions in serve mode. Bumped from the in-plugin default of 5 because each distinct caller now gets its own `sys-<hash>` session. |
128
- | `OPENCLAW_SERVE_TTL_MINUTES` | `60` | Idle TTL for OpenAI-compat sessions in serve mode. Idle sessions are reaped by a 60s background loop; persisted disk registry is kept for 7 days so a returning caller is auto-resumed. |
119
+ | Variable | Default | Purpose |
120
+ | ----------------------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
121
+ | `OPENCLAW_SERVER_TOKEN` | (unset) | Bearer token for HTTP auth. Set to enable; written to `~/.openclaw/server-token` for the CLI. |
122
+ | `OPENCLAW_RATE_LIMIT` | `300` | Max requests per IP per 60-second sliding window. |
123
+ | `OPENCLAW_CORS_ORIGINS` | (loopback only) | Set to `*` to allow all origins (the `/v1/*` paths already do this). |
124
+ | `OPENAI_COMPAT_NEW_CONVO_HEURISTIC` | (unset) | Set to `1` to enable webchat mode (see above). |
125
+ | `OPENAI_COMPAT_TOOLS_PER_MESSAGE` | (unset) | Set to `1` to re-send the full tool schemas on every turn (see [Tool definitions](#tool-definitions-and-where-they-live)). Needed only when the tool set changes mid-conversation; costs per-turn prompt growth. |
126
+ | `OPENAI_COMPAT_STATUS_URL` | (unset) | If set, the bridge POSTs JSON status updates to this URL (fire-and-forget, 2s timeout). See [Status webhook](#status-webhook). |
127
+ | `OPENCLAW_SERVE_MAX_SESSIONS` | `32` | Max concurrent OpenAI-compat sessions in serve mode. Bumped from the in-plugin default of 5 because each distinct caller now gets its own `sys-<hash>` session. |
128
+ | `OPENCLAW_SERVE_TTL_MINUTES` | `60` | Idle TTL for OpenAI-compat sessions in serve mode. Idle sessions are reaped by a 60s background loop; persisted disk registry is kept for 7 days so a returning caller is auto-resumed. |
129
129
 
130
130
  ## Inspection: `GET /v1/sessions`
131
131
 
@@ -235,14 +235,14 @@ Errors use the OpenAI error envelope:
235
235
  { "error": { "message": "...", "type": "invalid_request_error" } }
236
236
  ```
237
237
 
238
- | Status | When |
239
- |---|---|
240
- | 400 | `messages` empty/missing, no user message, invalid `max_tokens` |
241
- | 401 | Missing or wrong bearer token (when auth enabled) |
242
- | 415 | POST without `Content-Type: application/json` |
243
- | 429 | Rate limited (`OPENCLAW_RATE_LIMIT` exceeded) |
244
- | 503 | Failed to start a new session (model unavailable, CLI crashed at boot) |
245
- | 500 | Mid-turn failure |
238
+ | Status | When |
239
+ | ------ | ---------------------------------------------------------------------- |
240
+ | 400 | `messages` empty/missing, no user message, invalid `max_tokens` |
241
+ | 401 | Missing or wrong bearer token (when auth enabled) |
242
+ | 415 | POST without `Content-Type: application/json` |
243
+ | 429 | Rate limited (`OPENCLAW_RATE_LIMIT` exceeded) |
244
+ | 503 | Failed to start a new session (model unavailable, CLI crashed at boot) |
245
+ | 500 | Mid-turn failure |
246
246
 
247
247
  ## Related
248
248
 
@@ -16,7 +16,7 @@ start() → send() → send() → ... → stop()
16
16
  const info = await manager.startSession({
17
17
  name: 'my-task',
18
18
  cwd: '/path/to/project',
19
- model: 'opus', // alias or full name
19
+ model: 'opus', // alias or full name
20
20
  permissionMode: 'acceptEdits',
21
21
  effort: 'high',
22
22
  allowedTools: ['Bash', 'Read', 'Edit', 'Write'],
@@ -27,24 +27,24 @@ const info = await manager.startSession({
27
27
 
28
28
  Key options:
29
29
 
30
- | Option | Description |
31
- |--------|-------------|
32
- | `engine` | `'claude'` (default), `'codex'`, `'codex-app'`, `'agy'`, `'grok'`, `'opencode'`, or `'custom'` — see [Multi-Engine](./multi-engine.md) |
33
- | `model` | Model alias (`fable`, `opus`, `sonnet`, `haiku`, `agy-pro`) or full name |
34
- | `permissionMode` | `acceptEdits`, `bypassPermissions`, `plan`, `auto`, `manual`, `dontAsk` (`default` = legacy alias for `manual`) |
35
- | `effort` | `low`, `medium`, `high`, `max`, `auto` |
36
- | `bare` | Skip hooks, LSP, auto-memory, CLAUDE.md |
37
- | `worktree` | Run in isolated git worktree |
38
- | `appendSystemPrompt` | Append custom instructions to the system prompt |
30
+ | Option | Description |
31
+ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
32
+ | `engine` | `'claude'` (default), `'codex'`, `'codex-app'`, `'agy'`, `'grok'`, `'opencode'`, or `'custom'` — see [Multi-Engine](./multi-engine.md) |
33
+ | `model` | Model alias (`fable`, `opus`, `sonnet`, `haiku`, `agy-pro`) or full name |
34
+ | `permissionMode` | `acceptEdits`, `bypassPermissions`, `plan`, `auto`, `manual`, `dontAsk` (`default` = legacy alias for `manual`) |
35
+ | `effort` | `low`, `medium`, `high`, `max`, `auto` |
36
+ | `bare` | Skip hooks, LSP, auto-memory, CLAUDE.md |
37
+ | `worktree` | Run in isolated git worktree |
38
+ | `appendSystemPrompt` | Append custom instructions to the system prompt |
39
39
 
40
40
  ### Sending Messages
41
41
 
42
42
  ```typescript
43
43
  const result = await manager.sendMessage('my-task', 'Fix the auth bug', {
44
- effort: 'high', // override effort for this message
45
- plan: true, // enter plan mode
46
- timeout: 600_000, // 10 min timeout
47
- onChunk: (text) => process.stdout.write(text), // streaming
44
+ effort: 'high', // override effort for this message
45
+ plan: true, // enter plan mode
46
+ timeout: 600_000, // 10 min timeout
47
+ onChunk: (text) => process.stdout.write(text), // streaming
48
48
  });
49
49
 
50
50
  console.log(result.output);
@@ -100,8 +100,8 @@ Mid-turn and thread control via Codex 0.137 v2 RPCs, surfaced as tools: `codex_i
100
100
  Switch models mid-conversation. The session restarts with `--resume` to preserve history:
101
101
 
102
102
  ```typescript
103
- await manager.switchModel('my-task', 'haiku'); // fast model for simple tasks
104
- await manager.switchModel('my-task', 'opus'); // back to powerful model
103
+ await manager.switchModel('my-task', 'haiku'); // fast model for simple tasks
104
+ await manager.switchModel('my-task', 'opus'); // back to powerful model
105
105
  ```
106
106
 
107
107
  ### Tool Management
@@ -111,11 +111,11 @@ Add/remove tool permissions at runtime:
111
111
  ```typescript
112
112
  await manager.updateTools('my-task', {
113
113
  allowedTools: ['Bash', 'Read'],
114
- merge: true, // add to existing list
114
+ merge: true, // add to existing list
115
115
  });
116
116
 
117
117
  await manager.updateTools('my-task', {
118
- removeTools: ['Bash'], // revoke Bash access
118
+ removeTools: ['Bash'], // revoke Bash access
119
119
  });
120
120
  ```
121
121
 
@@ -152,7 +152,7 @@ against `turns`.
152
152
 
153
153
  Which outcome counts as a success is the engine's own verdict, not the exit
154
154
  code's: `codex` fails a turn that emits `turn.failed` while exiting 0, `agy`
155
- requires a `SUCCESS` status *and* a zero exit (it can report success and then die
155
+ requires a `SUCCESS` status _and_ a zero exit (it can report success and then die
156
156
  in cleanup), `gemini` succeeds on exit 53 because its turn limit resolves,
157
157
  `codex-app` requires `status: 'completed'` so a turn cancelled through
158
158
  `interrupt()` does not count, and `opencode` refuses a turn on purpose when
@@ -207,15 +207,15 @@ Session stats are returned by `getStats()` and surfaced through `coding_session_
207
207
 
208
208
  Fields added in plugin v2.13.0 (Claude CLI 2.1.111):
209
209
 
210
- | Field | Type | Description |
211
- |-------|------|-------------|
212
- | `retries` | number | Total API retries that occurred during the session |
213
- | `lastRetryError` | string \| undefined | Error message from the most recent retry (if any) |
210
+ | Field | Type | Description |
211
+ | ---------------- | ------------------- | -------------------------------------------------- |
212
+ | `retries` | number | Total API retries that occurred during the session |
213
+ | `lastRetryError` | string \| undefined | Error message from the most recent retry (if any) |
214
214
 
215
215
  Fields added in plugin v2.14.0 (Claude CLI 2.1.121):
216
216
 
217
- | Field | Type | Description |
218
- |-------|------|-------------|
217
+ | Field | Type | Description |
218
+ | -------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
219
219
  | `pluginErrors` | `Array<{plugin, reason}>` \| undefined | Plugins that failed to load due to unmet dependencies, captured from the `system/init` event. `undefined` when no plugin errors occurred. |
220
220
 
221
221
  ### `system/api_retry` events
@@ -238,3 +238,21 @@ All session engine classes expose an optional `pid` readonly property, providing
238
238
  const session = manager.getSession('my-session');
239
239
  console.log(session.pid); // e.g., 12345 or undefined
240
240
  ```
241
+
242
+ ## Verifying what a session did (6.0.0)
243
+
244
+ A plain session leaves no verdict — it ran, and nothing checked the result. To
245
+ check it, hand `verify_run` a contract and the directory:
246
+
247
+ ```jsonc
248
+ verify_run({
249
+ cwd: "/repo",
250
+ contract: { checks: [{ type: "command", cmd: "npm", args: ["test"] }] }
251
+ })
252
+ ```
253
+
254
+ For work that should be checked as part of running it, use a workflow instead —
255
+ see [`workflow.md`](./workflow.md).
256
+
257
+ `SendOptions` also gained `nodeKind` and `taskKind`, both stamped onto the run
258
+ ledger row. `taskKind` is caller-declared and never inferred from the prompt.