@enderfga/claw-orchestrator 7.14.0 → 7.15.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 +160 -135
- package/configs/autoloop-reviewer-prompt.md +1 -1
- package/configs/engines/README.md +31 -11
- package/dist/bin/acp-server.js +7 -12
- package/dist/bin/acp-server.js.map +1 -1
- package/dist/bin/cli.js +71 -40
- package/dist/bin/cli.js.map +1 -1
- package/dist/bin/mcp-server.js +2 -54
- package/dist/bin/mcp-server.js.map +1 -1
- package/dist/src/acp-server.d.ts +17 -9
- package/dist/src/acp-server.js +149 -30
- package/dist/src/acp-server.js.map +1 -1
- package/dist/src/autoloop/agent-tools.d.ts +2 -0
- package/dist/src/autoloop/agent-tools.js +7 -0
- package/dist/src/autoloop/agent-tools.js.map +1 -1
- package/dist/src/autoloop/dispatcher.d.ts +6 -0
- package/dist/src/autoloop/dispatcher.js +23 -1
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/planner-tools.js +2 -0
- package/dist/src/autoloop/planner-tools.js.map +1 -1
- package/dist/src/council.d.ts +8 -0
- package/dist/src/council.js +45 -12
- package/dist/src/council.js.map +1 -1
- package/dist/src/dashboard/index.html +28 -14
- package/dist/src/embedded-server.js +27 -8
- package/dist/src/embedded-server.js.map +1 -1
- package/dist/src/engine-spawn.js +3 -1
- package/dist/src/engine-spawn.js.map +1 -1
- package/dist/src/fanout.d.ts +13 -3
- package/dist/src/fanout.js +10 -0
- package/dist/src/fanout.js.map +1 -1
- package/dist/src/inbox-manager.d.ts +9 -0
- package/dist/src/inbox-manager.js +8 -2
- package/dist/src/inbox-manager.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +117 -51
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/conditions.d.ts +1 -1
- package/dist/src/kernel/conditions.js +1 -1
- package/dist/src/kernel/engine.d.ts +28 -8
- package/dist/src/kernel/engine.js +93 -19
- package/dist/src/kernel/engine.js.map +1 -1
- package/dist/src/kernel/nodes/agent.js +2 -0
- package/dist/src/kernel/nodes/agent.js.map +1 -1
- package/dist/src/kernel/nodes/council.js +9 -4
- package/dist/src/kernel/nodes/council.js.map +1 -1
- package/dist/src/kernel/nodes/fanout.js +5 -0
- package/dist/src/kernel/nodes/fanout.js.map +1 -1
- package/dist/src/kernel/nodes/goal.d.ts +8 -1
- package/dist/src/kernel/nodes/goal.js +40 -19
- package/dist/src/kernel/nodes/goal.js.map +1 -1
- package/dist/src/kernel/nodes/subflow.js +9 -1
- package/dist/src/kernel/nodes/subflow.js.map +1 -1
- package/dist/src/kernel/nodes/ultraapp.d.ts +1 -0
- package/dist/src/kernel/nodes/ultraapp.js +1 -0
- package/dist/src/kernel/nodes/ultraapp.js.map +1 -1
- package/dist/src/kernel/nodes/verifier.js +5 -1
- package/dist/src/kernel/nodes/verifier.js.map +1 -1
- package/dist/src/kernel/projections.d.ts +1 -0
- package/dist/src/kernel/projections.js +7 -1
- package/dist/src/kernel/projections.js.map +1 -1
- package/dist/src/kernel/templates/index.d.ts +6 -3
- package/dist/src/kernel/templates/index.js +8 -3
- package/dist/src/kernel/templates/index.js.map +1 -1
- package/dist/src/kernel/types.d.ts +30 -10
- package/dist/src/kernel/types.js.map +1 -1
- package/dist/src/logger.d.ts +6 -0
- package/dist/src/logger.js +14 -0
- package/dist/src/logger.js.map +1 -1
- package/dist/src/mcp-annotations.d.ts +13 -0
- package/dist/src/mcp-annotations.js +100 -0
- package/dist/src/mcp-annotations.js.map +1 -0
- package/dist/src/models.d.ts +7 -0
- package/dist/src/models.js +9 -0
- package/dist/src/models.js.map +1 -1
- package/dist/src/openai-compat.d.ts +11 -5
- package/dist/src/openai-compat.js +55 -54
- package/dist/src/openai-compat.js.map +1 -1
- package/dist/src/persistent-acp-session.d.ts +7 -0
- package/dist/src/persistent-acp-session.js +26 -2
- package/dist/src/persistent-acp-session.js.map +1 -1
- package/dist/src/persistent-codex-app-session.d.ts +2 -0
- package/dist/src/persistent-codex-app-session.js +18 -9
- package/dist/src/persistent-codex-app-session.js.map +1 -1
- package/dist/src/persistent-codex-session.d.ts +14 -3
- package/dist/src/persistent-codex-session.js +22 -19
- package/dist/src/persistent-codex-session.js.map +1 -1
- package/dist/src/persistent-custom-session.d.ts +9 -0
- package/dist/src/persistent-custom-session.js +41 -21
- package/dist/src/persistent-custom-session.js.map +1 -1
- package/dist/src/persistent-grok-session.js +1 -1
- package/dist/src/persistent-grok-session.js.map +1 -1
- package/dist/src/run-ledger.d.ts +3 -3
- package/dist/src/session-manager.d.ts +34 -1
- package/dist/src/session-manager.js +160 -30
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/types.d.ts +34 -11
- package/dist/src/types.js.map +1 -1
- package/dist/src/ultraapp/build.d.ts +5 -11
- package/dist/src/ultraapp/build.js +5 -11
- package/dist/src/ultraapp/build.js.map +1 -1
- package/dist/src/ultraapp/council-adapter.d.ts +5 -2
- package/dist/src/ultraapp/council-adapter.js +6 -6
- package/dist/src/ultraapp/council-adapter.js.map +1 -1
- package/dist/src/ultraapp/files.d.ts +7 -0
- package/dist/src/ultraapp/files.js +7 -1
- package/dist/src/ultraapp/files.js.map +1 -1
- package/dist/src/ultraapp/manager.d.ts +23 -3
- package/dist/src/ultraapp/manager.js +170 -72
- package/dist/src/ultraapp/manager.js.map +1 -1
- package/dist/src/ultraapp/narrator.js +1 -1
- package/dist/src/ultraapp/narrator.js.map +1 -1
- package/dist/src/ultraapp/versions.d.ts +27 -5
- package/dist/src/ultraapp/versions.js +58 -13
- package/dist/src/ultraapp/versions.js.map +1 -1
- package/openclaw.plugin.json +4 -13
- package/package.json +13 -6
- package/skills/SKILL.md +52 -24
- package/skills/references/acp.md +140 -61
- package/skills/references/autoloop.md +141 -35
- package/skills/references/claude-cli-tracking.md +8 -6
- package/skills/references/cli.md +122 -93
- package/skills/references/council.md +44 -22
- package/skills/references/dashboard.md +37 -21
- package/skills/references/getting-started.md +49 -15
- package/skills/references/inbox.md +13 -8
- package/skills/references/mcp.md +25 -17
- package/skills/references/multi-engine.md +161 -145
- package/skills/references/observability.md +90 -58
- package/skills/references/openai-compat.md +172 -82
- package/skills/references/sessions.md +101 -65
- package/skills/references/tools.md +209 -158
- package/skills/references/ultra.md +7 -7
- package/skills/references/ultraapp.md +101 -56
- package/skills/references/verification.md +112 -56
- package/skills/references/workflow.md +227 -93
- 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
|
|
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
|
-
|
|
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
|
|
35
|
-
| `effort` | `low`, `medium`, `high`, `xhigh`, `max`, `ultra`, `auto` — each engine clamps to its own ladder (see [Multi-Engine](./multi-engine.md))
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
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, //
|
|
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
|
-
|
|
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**:
|
|
58
|
-
- **Disk TTL**: 7 days
|
|
59
|
-
- **Auto-resume**: `startSession` with
|
|
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
|
|
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
|
-
//
|
|
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
|
|
106
|
-
written for the source engine — its model, tool allowlists in that engine's
|
|
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
|
|
114
|
-
conversation is sent. Past that, the opening request is kept, the newest turns fill what
|
|
115
|
-
and one line records how many turns in between were left out: the request says what the
|
|
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#
|
|
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
|
|
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
|
|
186
|
+
merge: true, // add to the existing lists
|
|
167
187
|
});
|
|
168
188
|
|
|
169
189
|
await manager.updateTools('my-task', {
|
|
170
|
-
removeTools: ['Bash'], //
|
|
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. `
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
|
219
|
-
in cleanup), `
|
|
220
|
-
`
|
|
221
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
271
|
-
2.
|
|
272
|
-
3.
|
|
273
|
-
4.
|
|
274
|
-
5.
|
|
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
|
|
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
|
|
283
|
-
|
|
|
284
|
-
| `retries`
|
|
285
|
-
| `lastRetryError`
|
|
286
|
-
| `pluginErrors`
|
|
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
|
-
|
|
306
|
-
|
|
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.
|