@enderfga/claw-orchestrator 7.14.0 → 7.16.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.
Files changed (145) hide show
  1. package/README.md +162 -135
  2. package/configs/autoloop-reviewer-prompt.md +1 -1
  3. package/configs/engines/README.md +31 -11
  4. package/dist/bin/acp-server.js +7 -12
  5. package/dist/bin/acp-server.js.map +1 -1
  6. package/dist/bin/cli.js +84 -46
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/bin/mcp-server.js +2 -54
  9. package/dist/bin/mcp-server.js.map +1 -1
  10. package/dist/src/acp-server.d.ts +17 -9
  11. package/dist/src/acp-server.js +149 -30
  12. package/dist/src/acp-server.js.map +1 -1
  13. package/dist/src/autoloop/agent-tools.d.ts +2 -0
  14. package/dist/src/autoloop/agent-tools.js +7 -0
  15. package/dist/src/autoloop/agent-tools.js.map +1 -1
  16. package/dist/src/autoloop/dispatcher.d.ts +6 -0
  17. package/dist/src/autoloop/dispatcher.js +72 -9
  18. package/dist/src/autoloop/dispatcher.js.map +1 -1
  19. package/dist/src/autoloop/planner-tools.js +2 -0
  20. package/dist/src/autoloop/planner-tools.js.map +1 -1
  21. package/dist/src/claude-goal-session.d.ts +147 -0
  22. package/dist/src/claude-goal-session.js +355 -0
  23. package/dist/src/claude-goal-session.js.map +1 -0
  24. package/dist/src/council.d.ts +8 -0
  25. package/dist/src/council.js +45 -12
  26. package/dist/src/council.js.map +1 -1
  27. package/dist/src/dashboard/index.html +28 -14
  28. package/dist/src/embedded-server.js +27 -8
  29. package/dist/src/embedded-server.js.map +1 -1
  30. package/dist/src/engine-spawn.js +3 -1
  31. package/dist/src/engine-spawn.js.map +1 -1
  32. package/dist/src/fanout.d.ts +13 -3
  33. package/dist/src/fanout.js +10 -0
  34. package/dist/src/fanout.js.map +1 -1
  35. package/dist/src/inbox-manager.d.ts +9 -0
  36. package/dist/src/inbox-manager.js +8 -2
  37. package/dist/src/inbox-manager.js.map +1 -1
  38. package/dist/src/index.d.ts +2 -0
  39. package/dist/src/index.js +135 -53
  40. package/dist/src/index.js.map +1 -1
  41. package/dist/src/kernel/agent-step.d.ts +6 -0
  42. package/dist/src/kernel/agent-step.js.map +1 -1
  43. package/dist/src/kernel/conditions.d.ts +1 -1
  44. package/dist/src/kernel/conditions.js +1 -1
  45. package/dist/src/kernel/engine.d.ts +28 -8
  46. package/dist/src/kernel/engine.js +93 -19
  47. package/dist/src/kernel/engine.js.map +1 -1
  48. package/dist/src/kernel/nodes/agent.js +2 -0
  49. package/dist/src/kernel/nodes/agent.js.map +1 -1
  50. package/dist/src/kernel/nodes/council.js +9 -4
  51. package/dist/src/kernel/nodes/council.js.map +1 -1
  52. package/dist/src/kernel/nodes/fanout.js +5 -0
  53. package/dist/src/kernel/nodes/fanout.js.map +1 -1
  54. package/dist/src/kernel/nodes/goal.d.ts +19 -5
  55. package/dist/src/kernel/nodes/goal.js +81 -34
  56. package/dist/src/kernel/nodes/goal.js.map +1 -1
  57. package/dist/src/kernel/nodes/subflow.js +9 -1
  58. package/dist/src/kernel/nodes/subflow.js.map +1 -1
  59. package/dist/src/kernel/nodes/ultraapp.d.ts +1 -0
  60. package/dist/src/kernel/nodes/ultraapp.js +1 -0
  61. package/dist/src/kernel/nodes/ultraapp.js.map +1 -1
  62. package/dist/src/kernel/nodes/verifier.js +5 -1
  63. package/dist/src/kernel/nodes/verifier.js.map +1 -1
  64. package/dist/src/kernel/projections.d.ts +1 -0
  65. package/dist/src/kernel/projections.js +7 -1
  66. package/dist/src/kernel/projections.js.map +1 -1
  67. package/dist/src/kernel/templates/index.d.ts +16 -11
  68. package/dist/src/kernel/templates/index.js +14 -8
  69. package/dist/src/kernel/templates/index.js.map +1 -1
  70. package/dist/src/kernel/types.d.ts +37 -13
  71. package/dist/src/kernel/types.js.map +1 -1
  72. package/dist/src/logger.d.ts +6 -0
  73. package/dist/src/logger.js +14 -0
  74. package/dist/src/logger.js.map +1 -1
  75. package/dist/src/mcp-annotations.d.ts +13 -0
  76. package/dist/src/mcp-annotations.js +100 -0
  77. package/dist/src/mcp-annotations.js.map +1 -0
  78. package/dist/src/models.d.ts +7 -0
  79. package/dist/src/models.js +9 -0
  80. package/dist/src/models.js.map +1 -1
  81. package/dist/src/openai-compat.d.ts +11 -5
  82. package/dist/src/openai-compat.js +55 -54
  83. package/dist/src/openai-compat.js.map +1 -1
  84. package/dist/src/persistent-acp-session.d.ts +7 -0
  85. package/dist/src/persistent-acp-session.js +26 -2
  86. package/dist/src/persistent-acp-session.js.map +1 -1
  87. package/dist/src/persistent-codex-app-session.d.ts +2 -0
  88. package/dist/src/persistent-codex-app-session.js +18 -9
  89. package/dist/src/persistent-codex-app-session.js.map +1 -1
  90. package/dist/src/persistent-codex-session.d.ts +14 -3
  91. package/dist/src/persistent-codex-session.js +22 -19
  92. package/dist/src/persistent-codex-session.js.map +1 -1
  93. package/dist/src/persistent-custom-session.d.ts +9 -0
  94. package/dist/src/persistent-custom-session.js +41 -21
  95. package/dist/src/persistent-custom-session.js.map +1 -1
  96. package/dist/src/persistent-grok-session.js +1 -1
  97. package/dist/src/persistent-grok-session.js.map +1 -1
  98. package/dist/src/persistent-session.d.ts +9 -0
  99. package/dist/src/persistent-session.js +23 -0
  100. package/dist/src/persistent-session.js.map +1 -1
  101. package/dist/src/run-ledger.d.ts +3 -3
  102. package/dist/src/session-manager.d.ts +45 -1
  103. package/dist/src/session-manager.js +199 -34
  104. package/dist/src/session-manager.js.map +1 -1
  105. package/dist/src/types.d.ts +34 -11
  106. package/dist/src/types.js.map +1 -1
  107. package/dist/src/ultraapp/build.d.ts +5 -11
  108. package/dist/src/ultraapp/build.js +5 -11
  109. package/dist/src/ultraapp/build.js.map +1 -1
  110. package/dist/src/ultraapp/council-adapter.d.ts +5 -2
  111. package/dist/src/ultraapp/council-adapter.js +6 -6
  112. package/dist/src/ultraapp/council-adapter.js.map +1 -1
  113. package/dist/src/ultraapp/files.d.ts +7 -0
  114. package/dist/src/ultraapp/files.js +7 -1
  115. package/dist/src/ultraapp/files.js.map +1 -1
  116. package/dist/src/ultraapp/manager.d.ts +23 -3
  117. package/dist/src/ultraapp/manager.js +170 -72
  118. package/dist/src/ultraapp/manager.js.map +1 -1
  119. package/dist/src/ultraapp/narrator.js +1 -1
  120. package/dist/src/ultraapp/narrator.js.map +1 -1
  121. package/dist/src/ultraapp/versions.d.ts +27 -5
  122. package/dist/src/ultraapp/versions.js +58 -13
  123. package/dist/src/ultraapp/versions.js.map +1 -1
  124. package/openclaw.plugin.json +4 -13
  125. package/package.json +13 -6
  126. package/skills/SKILL.md +52 -24
  127. package/skills/references/acp.md +140 -61
  128. package/skills/references/autoloop.md +148 -38
  129. package/skills/references/claude-cli-tracking.md +9 -6
  130. package/skills/references/cli.md +139 -108
  131. package/skills/references/council.md +44 -22
  132. package/skills/references/dashboard.md +37 -21
  133. package/skills/references/getting-started.md +49 -15
  134. package/skills/references/inbox.md +13 -8
  135. package/skills/references/mcp.md +25 -17
  136. package/skills/references/multi-engine.md +161 -145
  137. package/skills/references/observability.md +90 -58
  138. package/skills/references/openai-compat.md +172 -82
  139. package/skills/references/sessions.md +101 -65
  140. package/skills/references/tools.md +217 -158
  141. package/skills/references/ultra.md +7 -7
  142. package/skills/references/ultraapp.md +101 -56
  143. package/skills/references/verification.md +112 -56
  144. package/skills/references/workflow.md +275 -97
  145. package/assets/banner.jpg +0 -0
@@ -1,15 +1,15 @@
1
1
  # Sessions
2
2
 
3
- Sessions are the core abstraction — persistent, multi-turn coding conversations backed by a CLI subprocess.
3
+ Sessions are the core abstraction — persistent, multi-turn coding conversations backed by a coding CLI. `claude`, `codex-app`, `acp` and a persistent `custom` engine keep one process alive for the whole session; the one-shot engines (`codex`, `agy`, `grok`, `opencode`) start a process per send and resume their conversation by its id.
4
4
 
5
5
  ## Lifecycle
6
6
 
7
7
  ```
8
- start() → send() → send() → ... → stop()
9
- ↑ |
10
- └── resume (7-day TTL) ────┘
8
+ startSession() → sendMessage() → sendMessage() → ... → stopSession()
11
9
  ```
12
10
 
11
+ A session that sits idle past the memory TTL is unloaded, and one whose server restarts is gone from memory. Both stay in the resume registry for 7 days, and `startSession()` with the same name resumes the conversation. `stopSession()` ends a session for good: it removes the registry entry too, so the same name later starts a new conversation. See [Session Persistence](#session-persistence).
12
+
13
13
  ### Starting a Session
14
14
 
15
15
  ```typescript
@@ -25,49 +25,58 @@ const info = await manager.startSession({
25
25
  });
26
26
  ```
27
27
 
28
+ Starting a name that is already live returns that session's info unchanged. The number of live sessions is capped by `maxConcurrentSessions` (default 5; 32 under `clawo serve`).
29
+
28
30
  Key options:
29
31
 
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`, `xhigh`, `max`, `ultra`, `auto` — each engine clamps to its own ladder (see [Multi-Engine](./multi-engine.md)) |
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. Claude Code and Grok take it natively; Codex, Antigravity and OpenCode have no such flag and receive it at the top of the first message of a conversation |
32
+ | Option | Description |
33
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | `engine` | `'claude'` (default), `'codex'`, `'codex-app'`, `'agy'`, `'grok'`, `'opencode'`, `'acp'` (with `acpAgent`), or `'custom'` (with `customEngine`) — see [Multi-Engine](./multi-engine.md) |
35
+ | `model` | Model alias (`fable`, `opus`, `sonnet`, `haiku`, `agy-pro`, `agy-flash`, `grok`) or full name. A non-Claude model on the `claude` engine goes through the [multi-model proxy](#multi-model-proxy) |
36
+ | `permissionMode` | `acceptEdits` (default), `bypassPermissions`, `plan`, `auto`, `manual`, `dontAsk` (`default` = legacy alias for `manual`) |
37
+ | `effort` | `low`, `medium`, `high`, `xhigh`, `max`, `ultra`, `auto` (default) — each engine clamps to its own ladder (see [Multi-Engine](./multi-engine.md)) |
38
+ | `sandboxMode` | `read-only`, `workspace-write`, `danger-full-access`. `codex` and `codex-app` support all three (default `workspace-write`); `read-only` is enforced on `claude`, `agy`, `grok` and `opencode` through their own read-only modes; a custom engine must map it or the session refuses to start; an `acp` session refuses `read-only`, because the agent's tools run inside the agent |
39
+ | `maxBudgetUsd` | Spend cap in USD, checked before every send on every engine; once spend reaches it, further sends are refused |
40
+ | `bare` | Claude Code: skip hooks, LSP, auto-memory, CLAUDE.md |
41
+ | `worktree` | Claude Code: run in an isolated git worktree |
42
+ | `appendSystemPrompt` | Append custom instructions to the system prompt. Claude Code and Grok take it natively; Codex, Antigravity and OpenCode have no such flag and receive it at the top of the first message of a conversation, as does `codex-app` on a new thread and an `acp` session on a new session |
39
43
 
40
44
  ### Sending Messages
41
45
 
42
46
  ```typescript
43
47
  const result = await manager.sendMessage('my-task', 'Fix the auth bug', {
44
48
  effort: 'high', // override effort for this message
45
- plan: true, // enter plan mode
46
- timeout: 600_000, // 10 min timeout
49
+ plan: true, // ask for a plan only (claude and persistent custom engines)
50
+ timeout: 600_000, // 10 min timeout (default 5 min)
47
51
  onChunk: (text) => process.stdout.write(text), // streaming
48
52
  });
49
53
 
50
54
  console.log(result.output);
51
55
  ```
52
56
 
57
+ Sends to one session run one at a time: a second `sendMessage()` waits for the first to finish. `plan` puts a plan-only instruction in front of the message and leaves the permission mode unchanged; start the session with `sandboxMode: 'read-only'` to enforce it. The result carries `output`, the engine's resume id as `sessionId`, `error` when the engine reported the turn as failed, and `permissionDenials` / `images` when the turn had any.
58
+
59
+ `SendOptions` also accepts `parentRunId`, `nodeKind` and `taskKind`, all stamped onto the turn's run ledger row ([observability.md](./observability.md)). `taskKind` is caller-declared and never inferred from the prompt.
60
+
53
61
  ### Session Persistence
54
62
 
55
- Sessions automatically persist to `~/.openclaw/claude-sessions.json`:
63
+ A session is recorded in the resume registry, `~/.openclaw/claude-sessions.json` (the directory can be moved with `CLAWO_SESSIONS_DIR`), once its engine has reported a resume id:
56
64
 
57
- - **Memory TTL**: configurable (default 120 min) — idle sessions unloaded
58
- - **Disk TTL**: 7 days — sessions can be resumed after gateway restart
59
- - **Auto-resume**: `startSession` with the same name auto-resumes if a persisted session exists
65
+ - **Memory TTL**: an idle session is unloaded after `sessionTtlMinutes` (default 120; 60 under `clawo serve`). Its registry entry stays.
66
+ - **Disk TTL**: 7 days since the last activity.
67
+ - **Auto-resume**: `startSession` with a registered name resumes that conversation, restoring its engine, model, working directory, `sandboxMode`, `autoCompactPercent` and `chrome`. Other options — permission mode, tool lists, system prompts — are not stored; pass them again.
68
+ - **Not recorded**: sessions started with `noSessionPersistence`, and sessions whose engine has not reported a resume id yet.
60
69
 
61
70
  ### Session Resume & Fork
62
71
 
63
72
  ```typescript
64
- // Resume a specific Claude Code session
73
+ // Resume a conversation by the engine's own id
65
74
  await manager.startSession({
66
75
  name: 'continued',
67
76
  resumeSessionId: 'abc123-session-id',
68
77
  });
69
78
 
70
- // Fork for experiments (preserves history, new branch)
79
+ // Claude Code and Grok: fork it for experiments (preserves history, new branch)
71
80
  await manager.startSession({
72
81
  name: 'experiment',
73
82
  resumeSessionId: 'abc123',
@@ -75,6 +84,8 @@ await manager.startSession({
75
84
  });
76
85
  ```
77
86
 
87
+ `resumeSessionId` is the id the engine itself uses: a Claude Code session id, a Codex or `codex-app` thread id, an Antigravity conversation id, a Grok or OpenCode session id, an ACP agent's session id, or a custom engine's id when its config maps a resume flag. `forkSession` becomes `--fork-session` on Claude Code and Grok; it is a `SessionManager` option and not a `session_start` tool parameter.
88
+
78
89
  > `claude continue/respawn/stop/logs` are not headless subcommands — session continuation is via `resumeSessionId`/`forkSession`. Use the `claude_agents_list` tool (`claude agents --json`) to enumerate Claude Code background agent sessions.
79
90
 
80
91
  ### Handing off to another engine
@@ -91,6 +102,11 @@ await manager.handoffSession('refactor', {
91
102
  // → new session 'refactor-codex', same cwd; 'refactor' keeps running untouched
92
103
  ```
93
104
 
105
+ `newName` names the new session (default `<name>-<engine>`) and `model` picks its model (default:
106
+ that engine's default). The result reports the new session's `name` and `engine`, what was carried
107
+ as `carried: { turns, omitted, chars }`, and the reply as `result` when `message` was given. A
108
+ session with no recorded exchange yet cannot be handed off.
109
+
94
110
  **How the conversation travels.** No engine can resume another's session, and each keeps its
95
111
  history in its own undocumented on-disk format. So the conversation is replayed as text: a
96
112
  `<conversation_history>` block in front of the new session's first message, after which the new
@@ -102,18 +118,18 @@ store.
102
118
  recorded per session as it happens. The session's own history buffer is not used for this — it is
103
119
  capped by event count, and on a long session the opening request is the first thing it loses. The
104
120
  new session inherits the source's working directory and its engine-neutral settings (permission and
105
- sandbox mode, effort, spend cap, system prompts, extra directories). It does not inherit anything
106
- written for the source engine — its model, tool allowlists in that engine's tool names, resume ids,
107
- profiles.
121
+ sandbox mode, effort, spend cap, auto-compact threshold, system prompts, extra directories). It does
122
+ not inherit anything written for the source engine — its model, tool allowlists in that engine's
123
+ tool names, resume ids, profiles.
108
124
 
109
125
  **What does not.** The source engine's hidden reasoning, which no engine exposes, and the detail of
110
126
  tool calls — the new agent sees the replies that described the work, and the workspace itself, which
111
127
  the framing tells it to check before relying on anything the history describes.
112
128
 
113
- **When it is too long.** Up to `maxChars` (default 240,000 characters, ~60k tokens) the whole
114
- conversation is sent. Past that, the opening request is kept, the newest turns fill what is left,
115
- and one line records how many turns in between were left out: the request says what the work is
116
- for, the newest turns say where it stands, and the middle is what the workspace can answer.
129
+ **When it is too long.** Up to `maxChars` (default 240,000 characters, ~60k tokens; minimum 4,000)
130
+ the whole conversation is sent. Past that, the opening request is kept, the newest turns fill what
131
+ is left, and one line records how many turns in between were left out: the request says what the
132
+ work is for, the newest turns say where it stands, and the middle is what the workspace can answer.
117
133
 
118
134
  **A fork, not a move.** The two sessions go their separate ways. The new one starts from the
119
135
  source's record, so handing it off again carries the whole conversation rather than only its own
@@ -139,9 +155,11 @@ Code session. Read the outcome by sending a follow-up once the workflow has had
139
155
 
140
156
  Mid-turn and thread control via the Codex app-server v2 RPCs, surfaced as tools: `codex_interrupt` (cancel the in-flight turn), `codex_steer` (add input without restarting), `codex_fork` (branch the thread), `codex_rollback` (drop the last N turns), `codex_models` (list models + supported reasoning efforts), `codex_thread_list` (list the threads the session can see).
141
157
 
158
+ The same engine carries Codex's `/goal`: set and manage it on a session with the `codex_goal_*` tools, or run it as a checked workflow — goal, then verify, then repair on the same thread — with the `goal` node and `clawo goal` ([workflow.md](./workflow.md#goal)).
159
+
142
160
  ### Fan-out (cross-engine parallel)
143
161
 
144
- `fanout_start` runs one task across N engine/model agents in parallel and collects their answers (optional synthesis) — the best-of-N / diverse-perspective primitive. Unlike Council, no rounds/votes/worktrees; use Council for isolated parallel edits. See [tools.md](./tools.md#fan-out-3).
162
+ `fanout_start` runs one task across N engine/model agents in parallel and collects their answers (optional synthesis) — the best-of-N / diverse-perspective primitive. Unlike Council, no rounds/votes/worktrees; use Council for isolated parallel edits. See [tools.md](./tools.md#fanout_start).
145
163
 
146
164
  Each agent is a session and counts against `maxConcurrentSessions`. Fan-out and Council run no more agents at once than there are free slots when they start, and the rest wait for an agent to finish. When no slot is free at all, the agents fail on the cap rather than wait for sessions they do not own. On a memory-constrained host, the cap is the knob that bounds how many engine processes run at once. An aborted fan-out starts none of the agents still waiting.
147
165
 
@@ -149,28 +167,33 @@ Each agent is a session and counts against `maxConcurrentSessions`. Fan-out and
149
167
 
150
168
  ### Model Switching
151
169
 
152
- Switch models mid-conversation. The session restarts with `--resume` to preserve history:
170
+ Switch models mid-conversation with `switchModel()` (tool: `session_switch_model`). The session restarts on the new model and resumes the engine's own conversation by its resume id, so history is preserved:
153
171
 
154
172
  ```typescript
155
173
  await manager.switchModel('my-task', 'haiku'); // fast model for simple tasks
156
174
  await manager.switchModel('my-task', 'opus'); // back to powerful model
157
175
  ```
158
176
 
177
+ The switch is refused while a turn is running, for a model the registry does not know (a `provider/model` string is accepted), and for a session with no resume id yet — except `agy`, which then restarts fresh. If the restart fails, the session is restarted with its old configuration and the call throws. `setModel()` only changes the stored model, which takes effect at the next restart.
178
+
159
179
  ### Tool Management
160
180
 
161
- Add/remove tool permissions at runtime:
181
+ Add/remove tool permissions at runtime with `updateTools()` (tool: `session_update_tools`), which restarts the session the same way:
162
182
 
163
183
  ```typescript
164
184
  await manager.updateTools('my-task', {
165
185
  allowedTools: ['Bash', 'Read'],
166
- merge: true, // add to existing list
186
+ merge: true, // add to the existing lists
167
187
  });
168
188
 
169
189
  await manager.updateTools('my-task', {
170
- removeTools: ['Bash'], // revoke Bash access
190
+ removeTools: ['Bash'], // drop Bash from both lists
191
+ merge: true, // keep the rest of each list
171
192
  });
172
193
  ```
173
194
 
195
+ Without `merge: true` both lists are replaced, so a list you omit is cleared — including on a call that only passes `removeTools`. The busy and resume-id guards of `switchModel()` apply here too.
196
+
174
197
  ### Context Management
175
198
 
176
199
  ```typescript
@@ -184,11 +207,18 @@ console.log(`Context: ${status.stats.contextPercent}%`);
184
207
 
185
208
  `contextPercent` is how full the live context is right now — the current turn's
186
209
  prompt over the window the engine actually enforces — so it rises and falls with
187
- the conversation. `stats.tokensIn` is the different question of how many input
188
- tokens the session has been billed for in total, which only ever grows.
189
- `compactSession()` is a no-op on engines whose CLI has no compaction command
190
- (`codex`, `agy`, `grok`, `opencode`); those sessions log a warning the first
191
- time it is called.
210
+ the conversation. A persistent `custom` engine is the exception: there it is the
211
+ session's cumulative tokens over the engine config's `contextWindow` (200,000 when
212
+ unset), so it only grows (and a one-shot `custom` engine reports 0).
213
+ `stats.tokensIn` is the different question of how many input tokens the session
214
+ has been billed for in total, which only ever grows.
215
+
216
+ `compactSession()` runs `/compact` (with the optional summary) on `claude` and a
217
+ persistent `custom` engine, and starts the thread's own compaction on `codex-app`.
218
+ It is a no-op on the one-shot engines whose CLI has no compaction command (`codex`,
219
+ `agy`, `grok`, `opencode`), which log a warning the first time it is called, and a
220
+ silent no-op on `acp` agents, which manage their own context, and on one-shot
221
+ `custom` engines.
192
222
 
193
223
  A long-lived session can compact itself: start it with `autoCompactPercent`
194
224
  (1–99, off by default) and, before each send, the session is compacted first
@@ -215,11 +245,12 @@ against `turns`.
215
245
 
216
246
  Which outcome counts as a success is the engine's own verdict, not the exit
217
247
  code's: `codex` fails a turn that emits `turn.failed` while exiting 0, `agy`
218
- requires a `SUCCESS` status _and_ a zero exit (it can report success and then die
219
- in cleanup), `gemini` succeeds on exit 53 because its turn limit resolves,
220
- `codex-app` requires `status: 'completed'` so a turn cancelled through
221
- `interrupt()` does not count, and `opencode` refuses a turn on purpose when
222
- read-only enforcement did not load.
248
+ requires a `SUCCESS` status, a zero exit _and_ a non-empty reply (it can report
249
+ success and then die in cleanup), `codex-app` requires `status: 'completed'` so a
250
+ turn cancelled through `interrupt()` does not count, an `acp` agent must end the
251
+ turn with the stop reason `end_turn`, `opencode` refuses a turn on purpose when
252
+ read-only enforcement did not load, and the legacy `gemini` engine succeeds on
253
+ exit 53 because its turn limit resolves.
223
254
 
224
255
  **A succeeded turn is not a turn that did the work.** When the engine refuses a
225
256
  tool call it usually does not fail the turn. On Claude Code with
@@ -253,41 +284,46 @@ Built-in format translation lets Claude Code CLI talk to non-Anthropic models:
253
284
  - **Gemini** thought signature caching (round-trip thinking)
254
285
  - Auto-detect provider from model name patterns
255
286
 
256
- See [`src/proxy/`](https://github.com/Enderfga/claw-orchestrator/tree/main/src/proxy) for implementation details.
287
+ A `claude` session started with a non-Claude model and no `baseUrl` starts the proxy on a local port and points the session at it. Configuration is in the proxy section of [Multi-Engine](./multi-engine.md); see [`src/proxy/`](https://github.com/Enderfga/claw-orchestrator/tree/main/src/proxy) for implementation details.
257
288
 
258
289
  ## Circuit Breaker
259
290
 
260
- SessionManager tracks consecutive failures per engine type. After 3 consecutive start failures for an engine, a circuit breaker opens with exponential backoff (1s × 2^(n-1), capped at 5 minutes). During backoff, new session creation for that engine is rejected with a descriptive error.
291
+ SessionManager tracks consecutive failures per engine type. After 3 consecutive start failures for an engine, a circuit breaker opens with exponential backoff (1s × 2^(n-1) after the n-th consecutive failure, capped at 5 minutes). During backoff, new session creation for that engine is rejected with a descriptive error.
261
292
 
262
293
  - Resets on successful session start
263
- - State visible in `health()` response under `circuitBreakers`
294
+ - State visible in the `health()` response (the `sessions_overview` tool) under `circuitBreakers`
264
295
  - Constants: `CIRCUIT_BREAKER_THRESHOLD` (3), `CIRCUIT_BREAKER_BACKOFF_BASE_MS` (1s), `CIRCUIT_BREAKER_MAX_BACKOFF_MS` (5 min)
265
296
 
266
297
  ## Orphaned Process Cleanup
267
298
 
268
- If the plugin crashes without calling `stop()`, child CLI processes (claude, codex, agy, grok, opencode, and legacy engines) may become orphans. SessionManager tracks PIDs in `~/.openclaw/session-pids.json` and cleans up stale processes on startup:
299
+ If the process that owns the sessions exits without stopping them, their engine processes can outlive it. When a session starts, SessionManager records the PID of its engine process in `~/.openclaw/session-pids.json`, together with its own PID, and cleans up on startup:
269
300
 
270
- 1. Reads PID file from previous run
271
- 2. For each PID, checks if process is alive (`kill -0`)
272
- 3. Verifies the process command line matches a known CLI binary (prevents killing recycled PIDs)
273
- 4. Sends SIGTERM, then SIGKILL after 3 seconds
274
- 5. Clears the PID file
301
+ 1. Reads the PID file left by earlier runs
302
+ 2. Skips entries whose owning SessionManager is still alive, and old-format entries that name no owner
303
+ 3. For each remaining PID, checks if the process is alive (`kill -0`)
304
+ 4. Verifies the process command line names a known engine binary — `claude`, `codex`, `agy`, `grok`, `opencode`, or the legacy `gemini` / `cursor-agent` / `agent` — so a recycled PID is never killed
305
+ 5. Sends SIGTERM to the process group and the process, then SIGKILL after 3 seconds
306
+ 6. Rewrites the PID file without the stale entries
307
+
308
+ The one-shot engines have no process running when a session starts, so they have no PID to record. ACP agents and persistent custom engines are recorded, but their binaries are not in the known list, so an orphaned one is left running.
275
309
 
276
310
  ## Stats & Monitoring
277
311
 
278
- Session stats are returned by `getStats()` and surfaced through `coding_session_status`.
312
+ Session stats are `manager.getStatus(name).stats`, surfaced through `coding_session_status`.
279
313
 
280
314
  Claude Code–specific stats:
281
315
 
282
- | Field | Type | Description |
283
- | ---------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
284
- | `retries` | number | Total API retries that occurred during the session |
285
- | `lastRetryError` | string \| undefined | Error message from the most recent retry (if any) |
286
- | `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. |
316
+ | Field | Type | Description |
317
+ | --------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
318
+ | `retries` | number | Total API retries that occurred during the session (present on every engine; only Claude Code reports retries) |
319
+ | `lastRetryError` | string \| undefined | Error category of the most recent retry (e.g. `overloaded`, `rate_limit`), or its HTTP status when the CLI gives no category |
320
+ | `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. |
321
+ | `refusalFallbacks` | number \| undefined | Turns the model refused and the CLI re-ran on another model (`system/model_refusal_fallback`). `undefined` when none happened |
322
+ | `lastRefusalFallback` | `{from, to, category?}` \| undefined | The most recent of those: the refusing model, the model that re-ran the turn, and the refusal category when the CLI reports one |
287
323
 
288
324
  ### `system/api_retry` events
289
325
 
290
- When Claude Code CLI performs an API retry (transient errors, overload, etc.) it emits a `system` event with subtype `api_retry`. The plugin parses these events and increments `retries` / updates `lastRetryError` in the session stats. These events are also visible in the session event history returned by `session_grep`.
326
+ When Claude Code CLI performs an API retry (transient errors, overload, etc.) it emits a `system` event with subtype `api_retry`. The plugin parses these events and increments `retries` / updates `lastRetryError` in the session stats. These events are also visible in the session event history returned by `session_grep`, which holds the session's last 100 events.
291
327
 
292
328
  ```typescript
293
329
  const status = manager.getStatus('my-task');
@@ -299,11 +335,14 @@ if (status.stats.lastRetryError) {
299
335
 
300
336
  ## ISession.pid
301
337
 
302
- All session engine classes expose an optional `pid` readonly property, providing the OS process ID of the underlying CLI subprocess. Returns `undefined` when no process is running.
338
+ All session engine classes expose an optional `pid` readonly property, providing the OS process ID of the underlying CLI subprocess. Returns `undefined` when no process is running — on the one-shot engines, between sends. SessionManager reads it for [orphan cleanup](#orphaned-process-cleanup) and does not expose it; read it from an engine class you construct yourself:
303
339
 
304
340
  ```typescript
305
- const session = manager.getSession('my-session');
306
- console.log(session.pid); // e.g., 12345 or undefined
341
+ import { PersistentClaudeSession } from '@enderfga/claw-orchestrator';
342
+
343
+ const session = new PersistentClaudeSession({ name: 'probe', cwd: '/path/to/project', permissionMode: 'acceptEdits' });
344
+ await session.start();
345
+ console.log(session.pid); // e.g., 12345
307
346
  ```
308
347
 
309
348
  ## Verifying what a session did
@@ -320,6 +359,3 @@ verify_run({
320
359
 
321
360
  For work that should be checked as part of running it, use a workflow instead —
322
361
  see [`workflow.md`](./workflow.md).
323
-
324
- `SendOptions` also accepts `nodeKind` and `taskKind`, both stamped onto the run
325
- ledger row. `taskKind` is caller-declared and never inferred from the prompt.