Skip to content

Commit 347bf7d

Browse files
author
cloudsigma
committed
refactor: remove trash sweeper, stuck-run status, zombie auto-abort, and background timers
Streamline the plugin to focus on its core purpose: - Session affinity (session_id, sticky_key metadata injection) - X-Session-Id transport header - Requester runtime metadata (sanitized for TaaS proxy) - Autorouter response-header capture + taas.autorouter.lastRoute gateway RPC - Add activation.onStartup for reliable plugin loading Removed dead-end features that belong in the TaaS proxy layer, not the OpenClaw plugin: - Trash sweeper (periodic run-directory cleanup) - Stuck-run status writer (JSON status files) - Zombie auto-abort (detect idle runs, emit chat.abort) - Background timer scheduler (setInterval orchestration) These features were either no-ops (zombie abort had no SDK dispatch capability) or redundant with the TaaS proxy's own lifecycle management.
1 parent 4195ffc commit 347bf7d

7 files changed

Lines changed: 243 additions & 1439 deletions

File tree

README.md

Lines changed: 102 additions & 205 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,57 @@
1-
# openclaw-token-cache-optimizer
1+
# openclaw-taas-affinity
22

3-
An [OpenClaw](https://openclaw.ai) provider plugin that maximises prompt-cache hit rates when using [CloudSigma TaaS](https://www.cloudsigma.com) as your LLM provider.
3+
CloudSigma TaaS affinity provider hook for OpenClaw.
44

5-
It injects a stable, per-conversation session ID into every outbound request so TaaS can pin your conversation to the same upstream slot (OAuth token, Bedrock region, or Claude Code node) from the very first turn — giving you consistent prompt-cache reuse instead of cold starts on every message.
5+
This plugin is intentionally narrow after the Claude Code Direction-2 lane update. It does **not** lease requester bridges, poll for bridge work, invoke requester-local tools, rewrite OpenAI tool payloads, or run OpenClaw maintenance sidecars.
66

7-
The plugin is intentionally narrow: requester-side tool execution is handled by Claude Code, TaaS, and the OpenClaw gateway Direction-2 path. This plugin does **not** lease requester bridges, poll for bridge work, invoke local tools, or alter OpenAI tool payloads.
7+
## What it does
88

9-
---
9+
For requests routed through the `cloudsigma` or `cloudsigma-staging` provider IDs, the plugin:
1010

11-
## The problem it solves
11+
- derives a stable affinity session ID with the form `oc:<sha256-prefix>`
12+
- injects `metadata.session_id` when absent
13+
- injects `metadata.sticky_key` when absent
14+
- injects a sanitized `metadata.requester_runtime` envelope when absent
15+
- injects transport header `X-Session-Id`
16+
- captures TaaS autorouter response headers
17+
- exposes the latest route capture via gateway method `taas.autorouter.lastRoute`
1218

13-
TaaS routes LLM requests across a pool of upstream slots. Without a session signal, it uses heuristics to guess which requests belong to the same conversation:
19+
## Startup compatibility
1420

15-
| Method | Confidence | Works when |
16-
|---|---:|---|
17-
| Tool-use ID chain | 1.0 | Tool-result follow-up turns only |
18-
| Structural inference | 0.85 | Mid-conversation, after a few turns |
19-
| New session fallback | 0.30 | First turn — no prior context |
20-
21-
That **0.30 confidence on turn 1** means the first message in every conversation is likely routed to a random slot, breaking prompt-cache continuity right from the start.
21+
The manifest explicitly asks OpenClaw to import the plugin at gateway startup:
2222

23-
This plugin passes a stable `session_id` derived from your OpenClaw workspace so TaaS short-circuits heuristic matching and achieves **confidence 1.0 from turn 1**.
23+
```json
24+
{
25+
"activation": {
26+
"onStartup": true,
27+
"onProviders": ["cloudsigma", "cloudsigma-staging"]
28+
}
29+
}
30+
```
2431

25-
---
32+
This is required because gateway RPC handlers must be attached during gateway startup. Provider/lazy activation is not enough for `taas.autorouter.lastRoute` to be present in the live gateway dispatch table.
2633

27-
## How it works
34+
## Request metadata
2835

29-
OpenClaw's `wrapStreamFn` hook intercepts the outbound request payload before it is sent to TaaS. The plugin adds session affinity fields plus a small sanitized requester runtime envelope:
36+
Example injected metadata:
3037

3138
```json
3239
{
3340
"metadata": {
34-
"session_id": "oc:edebc39a82a8a041",
35-
"sticky_key": "oc:edebc39a82a8a041",
41+
"session_id": "oc:0123456789abcdef",
42+
"sticky_key": "oc:0123456789abcdef",
3643
"requester_runtime": {
37-
"schema_version": "2026-06-03",
38-
"source": "openclaw-token-cache-optimizer",
39-
"session_key": "oc:edebc39a82a8a041",
40-
"openclaw_session_id": "oc:edebc39a82a8a041",
44+
"schema_version": "2026-06-04",
45+
"source": "openclaw-taas-affinity",
46+
"session_key": "oc:0123456789abcdef",
47+
"openclaw_session_id": "oc:0123456789abcdef",
4148
"requester_host_id": "host:1a2b3c4d5e6f7890",
42-
"repo_name": "my-repo",
49+
"repo_name": "example-repo",
4350
"git_branch_hint": "dev",
4451
"git_dirty_hint": false,
4552
"provider": "cloudsigma",
4653
"model_id": "cloudsigma/auto",
47-
"session_source_hint": "source:4ae2870a2e73027c",
54+
"session_source_hint": "source:1a2b3c4d5e6f7890",
4855
"tool_execution": "direction_2_gateway",
4956
"metadata_classification": {
5057
"identifiers": "hashed",
@@ -57,231 +64,121 @@ OpenClaw's `wrapStreamFn` hook intercepts the outbound request payload before it
5764
}
5865
```
5966

60-
- `session_id` — read by TaaS's OpenAI and Codex affinity paths.
61-
- `sticky_key` — additionally read by the Anthropic substrate routing layer.
62-
- `requester_runtime` — safe advisory hints for downstream routing and diagnostics.
63-
- `X-Session-Id` — injected by `resolveTransportTurnState` for transports that support per-turn native headers.
64-
65-
All metadata fields are no-overwrite: if the caller already supplied `metadata.session_id`, `metadata.sticky_key`, or `metadata.requester_runtime`, the plugin leaves them intact.
67+
All metadata fields are no-overwrite. If the caller already supplied `metadata.session_id`, `metadata.sticky_key`, or `metadata.requester_runtime`, the plugin leaves them intact.
6668

67-
### Requester runtime metadata
69+
The plugin does not include raw local paths (`workspace_dir`, `agent_dir`, `repo_root_hint`), environment variables, tokens, git remotes, full git status output, diffs, or arbitrary provider `extraParams`.
6870

69-
The runtime envelope is intentionally small and sanitized. By default it contains:
71+
## Direction-2 tool handling
7072

71-
- required affinity/session fields: `session_key`, `openclaw_session_id`
72-
- hashed identifiers: `requester_host_id`, `session_source_hint`
73-
- bounded repo hints when available: `repo_name`, `git_branch_hint`, `git_dirty_hint`
74-
- provider/model hints when available: `provider`, `model_id`
75-
- explicit execution-mode marker: `tool_execution: "direction_2_gateway"`
76-
- metadata classification and redaction policy
73+
Requester-side tool execution is handled outside this plugin by the Claude Code / TaaS / OpenClaw Direction-2 path.
7774

78-
It does **not** include raw local paths (`workspace_dir`, `agent_dir`, `repo_root_hint`) by default. It also never includes environment variables, tokens, git remotes, full status output, diffs, or arbitrary provider `extraParams`. Git probes are bounded with a short timeout.
75+
The plugin intentionally leaves these payload structures untouched:
7976

80-
### Tool execution model: Direction-2
77+
- OpenAI `tools`
78+
- `tool_choice`
79+
- assistant `tool_calls`
80+
- `role: "tool"` continuation messages
8181

82-
Requester-side tools are handled outside this plugin by Claude Code / TaaS / OpenClaw gateway Direction-2. Consequently this plugin:
82+
It also does not inject:
8383

84-
- does not call `/internal/requester-bridges/leases`
85-
- does not inject `requester_runtime.available_bridges`
86-
- does not set `capture_mode: "bridge_capable"`
87-
- does not poll `/internal/requester-bridges/poll` or post `/internal/requester-bridges/results`
88-
- does not invoke requester-local `/tools/invoke`
89-
- does not intercept OpenAI `tools`, `tool_calls`, or `role: "tool"` messages
84+
- `requester_runtime.available_bridges`
85+
- `capture_mode: "bridge_capable"`
86+
- bridge operation names such as `requester.tool.invoke`, `openclaw.tool.invoke`, `bridge.ping`, or `bridge.echo`
9087

91-
### Session ID derivation
88+
## Autorouter route capture
9289

93-
The ID is a SHA-256 hash of the session source, truncated to 16 hex chars and prefixed `oc:`. The plugin walks through a tier list to find the best available source:
90+
TaaS may return response headers such as:
9491

95-
| Tier | Source | Notes |
96-
|---|---|---|
97-
| 1 | `ctx.workspaceDir` (explicit) | Best signal — populated for main agent and many subagents |
98-
| 2 | `globalThis[pluginRegistryState].workspaceDir` | Parent agent workspace via plugin registry |
99-
| 3 | `process.env.OPENCLAW_SESSION_ID` | If OpenClaw sets this env var for sub-agents in future |
100-
| 4 | `process.env.OPENCLAW_AGENT_ID` / `OPENCLAW_RUN_ID` | Any stable per-agent env var |
101-
| 5 | `OPENCLAW_STATE_DIR` hash | Per-installation fallback — least specific |
92+
- `X-TaaS-Autorouted: true`
93+
- `X-TaaS-Autorouter-Model`
94+
- `X-TaaS-Autorouter-Mode`
95+
- `X-TaaS-Autorouter-Algorithm-Source`
96+
- `X-TaaS-Thinking-Applied`
97+
- `X-TaaS-Routed-Context-Window`
10298

103-
| Property | Detail |
104-
|---|---|
105-
| **Stable** | Same value for every API turn within one conversation |
106-
| **Unique** | Different workspaces / env vars → different IDs |
107-
| **Resets on `/new`** | New conversation = new workspace = new ID |
108-
| **Namespaced** | `oc:` prefix avoids collision with Claude Code and other TaaS clients |
99+
The plugin stores the latest bounded capture per affinity session and per derived OpenClaw agent ID.
109100

110-
### Autorouter capture
101+
Query latest route by agent:
111102

112-
The wrapper captures TaaS `X-TaaS-*` response headers for autorouted requests and exposes the most recent route via the gateway RPC:
113-
114-
```text
115-
taas.autorouter.lastRoute
103+
```bash
104+
openclaw gateway call taas.autorouter.lastRoute \
105+
--params '{"agentId":"new-agent-2"}' \
106+
--json
116107
```
117108

118-
Callers can query by `workspaceDir`, direct `sessionId`, or `agentId`. Captured values include the autorouted model, algorithm/mode, algorithm source, thinking level applied, and routed context window.
119-
120-
### Sub-agent behaviour
121-
122-
OpenClaw sub-agents run in isolated processes and may not receive a `workspaceDir` in their `wrapStreamFn` context. The tier fallback system ensures sub-agents always get a deterministic session ID:
123-
124-
1. **If the sub-agent has a workspace** (Tier 1) — derives a unique ID from it.
125-
2. **If the parent agent workspace is visible** via globalThis (Tier 2) — reuses the parent's ID.
126-
3. **If OpenClaw injects env vars** (Tiers 3–4) — uses those for a stable per-agent ID.
127-
4. **Last resort** (Tier 5) — falls back to the state dir hash.
128-
129-
#### Debug logging
130-
131-
Set `OPENCLAW_DEBUG=1` (or `NODE_ENV=development`) to emit the session ID source on each request:
109+
Query by explicit affinity session ID:
132110

133-
```text
134-
[taas-affinity] wrapStreamFn sessionId=oc:edebc39a82a8a041 source=workspaceDir:/home/user/.openclaw/...
135-
[taas-affinity] resolveTransportTurnState sessionId=oc:edebc39a82a8a041 source=workspaceDir:... turnId=abc attempt=1
111+
```bash
112+
openclaw gateway call taas.autorouter.lastRoute \
113+
--params '{"sessionId":"oc:0123456789abcdef"}' \
114+
--json
136115
```
137116

138-
---
139-
140-
## Requirements
141-
142-
- **OpenClaw** ≥ 2026.4.27
143-
- Requires the provider plugin hooks exposed via `openclaw/plugin-sdk/core`, including `wrapStreamFn`, `hookAliases`, and `resolveTransportTurnState`.
144-
- **Node.js** 22+
145-
- **TaaS** with session-affinity short-circuit support (commit `61a9960`+, April 2026)
146-
- A CloudSigma account with TaaS access
147-
148-
Older OpenClaw builds may fail to load the plugin or may load it without applying the transport/header hook. Upgrade OpenClaw before deploying this plugin to production instances.
149-
150-
---
151-
152-
## Installation
153-
154-
### Option 1 - npm install
117+
Query by workspace path, deriving the same affinity session ID as the wrapper:
155118

156119
```bash
157-
openclaw plugins install openclaw-token-cache-optimizer
158-
openclaw gateway restart
120+
openclaw gateway call taas.autorouter.lastRoute \
121+
--params '{"workspaceDir":"/home/cloudsigma/.openclaw/workspace-new-agent-2"}' \
122+
--json
159123
```
160124

161-
The published npm package ships pre-built JavaScript in `dist/` and works on OpenClaw `2026.4.27` and later.
162-
163-
### Option 2 - manual install from source
125+
## Install / update from checkout
164126

165127
```bash
166-
git clone https://github.com/cloudsigma/openclaw-token-cache-optimizer \
167-
~/.openclaw/extensions/openclaw-token-cache-optimizer
168-
cd ~/.openclaw/extensions/openclaw-token-cache-optimizer
169-
git checkout dev
128+
cd /home/cloudsigma/openclaw-taas-affinity
170129
npm ci
130+
npm test
171131
npm run build
172-
openclaw gateway restart
132+
rsync -a --delete \
133+
--exclude '.git' \
134+
--exclude 'node_modules' \
135+
--exclude 'package-lock.json' \
136+
./ ~/.openclaw/extensions/openclaw-taas-affinity/
137+
cd ~/.openclaw/extensions/openclaw-taas-affinity
138+
npm ci --omit=dev --ignore-scripts
173139
```
174140

175-
`npm ci` runs the `prepare` lifecycle script, which compiles TypeScript to `dist/index.js`. Re-run `npm run build` after pulling new source changes.
141+
Then restart the managed gateway service during a controlled window:
176142

177-
No `openclaw.json` changes are required - the plugin auto-activates for all requests to the `cloudsigma` and `cloudsigma-staging` providers.
143+
```bash
144+
systemctl --user restart openclaw-gateway.service
145+
```
178146

179-
### Verify it loaded
147+
Verify:
180148

181149
```bash
182150
openclaw gateway status
183151
openclaw plugins info openclaw-taas-affinity
152+
openclaw gateway call taas.autorouter.lastRoute --params '{"agentId":"new-agent-2"}' --json
184153
```
185154

186-
You should see `Status: loaded` and the source pointing at `dist/index.js`.
187-
188-
---
189-
190-
## Compatibility
191-
192-
| OpenClaw gateway | Status | Notes |
193-
|---|---|---|
194-
| >= 2026.5.x | Supported | Loads compiled `dist/index.js` |
195-
| 2026.4.27 - 2026.4.x | Supported | Loads compiled `dist/index.js`; transport/header support depends on gateway hook availability |
196-
| < 2026.4.27 | Not supported | Hooks the plugin relies on are not exposed |
197-
198-
---
199-
200-
## Verification
201-
202-
### Local validation
203-
204-
For repository/CI validation, install dev dependencies and run the test suite:
155+
## Development
205156

206157
```bash
207-
npm ci
158+
npm run typecheck
159+
npm run smoke
160+
npm run unit
208161
npm test
209162
npm run build
210163
```
211164

212-
This runs:
213-
214-
- `npm run typecheck` — validates the TypeScript source against the OpenClaw plugin SDK and Node typings
215-
- `npm run smoke` — imports the plugin, registers the provider hook, and verifies payload/header injection plus autorouter capture
216-
- `npm run unit` — validates sweeper/status/auto-abort behaviour and Direction-2 regressions
217-
218-
Direction-2 regression coverage includes:
219-
220-
- requester bridge lease endpoint is not called
221-
- `available_bridges` and bridge capture metadata are not injected
222-
- OpenAI `tools` pass through untouched
223-
- assistant `tool_calls` and `role: "tool"` messages are not intercepted
224-
- existing metadata fields are not overwritten
165+
Current tests cover:
225166

226-
### TaaS logs
167+
- manifest startup activation
168+
- provider hook registration for `cloudsigma` and `cloudsigma-staging`
169+
- metadata/header injection
170+
- no-overwrite behavior
171+
- absence of requester bridge descriptors
172+
- OpenAI tool payload pass-through
173+
- autorouter capture and lookup by workspace/session/agent
227174

228-
After installing, the first turn of every new conversation should show:
229-
230-
```text
231-
match_reason: "external_id_new" ← first turn (new session in Redis)
232-
match_reason: "external_id" ← subsequent turns (known session)
233-
```
234-
235-
Previously turn 1 would show `match_reason: "new"` with `confidence: 0.30`.
236-
237-
### Redis (from a TaaS pod)
238-
239-
```bash
240-
redis-cli -h redis.taas.svc.cluster.local get "anth:session:oc:edebc39a82a8a041"
241-
```
242-
243-
Replace the ID with your actual session ID. A non-null response confirms TaaS has bound the session to a slot.
244-
245-
---
246-
247-
## Behaviour by session type
248-
249-
| Session type | ID scope |
250-
|---|---|
251-
| Main agent | Own stable ID for the conversation lifetime |
252-
| Spawned subagent | Own ID when a separate `workspaceDir` is present; otherwise tier fallback |
253-
| Cron / isolated run | Own ID when an isolated workspace/env source exists |
254-
| New conversation (`/new`, `/reset`) | New workspace → new ID |
255-
| Parallel conversations | Each gets a separate ID when OpenClaw supplies separate workspaces/env sources |
256-
257-
---
258-
259-
## Configuration
260-
261-
None required for standard CloudSigma TaaS use.
262-
263-
Supported environment variables:
175+
## Environment variables
264176

265177
| Variable | Default | Purpose |
266-
|---|---|---|
178+
|---|---:|---|
267179
| `OPENCLAW_DEBUG` | unset | Emit debug logs for session source and autorouter capture |
268-
| `OPENCLAW_SESSION_ID` | unset | Optional session source fallback |
269-
| `OPENCLAW_AGENT_ID` / `OPENCLAW_RUN_ID` | unset | Optional per-agent session source fallback |
270-
| `OPENCLAW_STATE_DIR` | `~/.openclaw` | Last-resort stable source for fallback session ID |
271-
| `TAAS_AFFINITY_SWEEP_INTERVAL_MS` | `3600000` | Background trash sweeper interval |
272-
| `TAAS_AFFINITY_SWEEP_STALE_DAYS` | `7` | Age threshold for stale `.deleted` agent directories |
273-
| `TAAS_AFFINITY_RUNS_STATUS_PATH` | `~/.openclaw/alien-studio/runs-status.json` | Stuck-run status JSON path |
274-
| `TAAS_AFFINITY_AUTO_ABORT_ZOMBIES` | `false` | Opt-in zombie run auto-abort check |
275-
| `TAAS_AFFINITY_AUTO_ABORT_DRY_RUN` | `false` | Log zombie abort candidates without aborting |
180+
| `OPENCLAW_SESSION_ID` | unset | Preferred stable session source when supplied by OpenClaw |
181+
| `OPENCLAW_AGENT_ID` / `OPENCLAW_RUN_ID` | unset | Fallback stable agent/session source |
182+
| `OPENCLAW_STATE_DIR` | `~/.openclaw` | Last-resort stable fallback source |
276183

277184
Requester bridge variables such as `TAAS_REQUESTER_BRIDGE_PLUGIN_ENABLED`, `TAAS_REQUESTER_BRIDGE_LEASE_URL`, and `TAAS_REQUESTER_BRIDGE_POLL_INTERVAL_MS` are obsolete and ignored by this plugin version.
278-
279-
---
280-
281-
## Contributing
282-
283-
Issues and PRs welcome. The core logic lives in [`index.ts`](./index.ts).
284-
285-
## License
286-
287-
MIT — see [LICENSE](./LICENSE).

0 commit comments

Comments
 (0)