Tools
Session Placement
OpenClaw plugin: safe remote task-session placement on session-host nodes
Install
npm install
npm
README
# openclaw-session-placement
An OpenClaw plugin that gives a trusted coordinator agent a **safe, narrow way to run
bounded task sessions on paired session-host nodes** — via official Gateway APIs only.
> **Agent quickstart:** your tools are `placement_start`, `placement_status`,
> `placement_list`, `placement_cancel`, `placement_reclaim`. One call per task, keyed by
> `idempotencyKey`. Same key + same request → same placement, no duplicate work.
> Same key + different request → conflict, nothing runs. The worker receives one task
> envelope (`BEGIN_TASK_ENVELOPE_V1` … `END_TASK_ENVELOPE_V1`) and must finish with one
> result envelope (`BEGIN_PLACEMENT_RESULT_V1` … `END_PLACEMENT_RESULT_V1`); a run
> without a valid result is `invalid_terminal_evidence`.
## Why this exists
OpenClaw already knows how to run remote sessions, but a lead agent should not hold
arbitrary Gateway RPC, pick any agent/node/model, or silently fall back to running
workers on the control-plane host. This plugin is the narrow, auditable shell around
the official operations:
```
Lead agent (control plane)
│ placement_start(role, repository, manifest)
▼
session-placement plugin (same Gateway)
│ sessions.create → sessions.dispatch → sessions.send
│ observe / cancel / reclaim / reconcile
▼
paired session-host node — ephemeral Coder / QA worker
```
No MCP dependency, no shell, no Docker control, no secrets in tool arguments.
## What it does
1. Accepts a strictly-typed request to run **one role** (`placement_start`).
2. Picks an allowed agent ID from the role's configured pool (least-loaded, stable tie-break).
3. Admits against per-agent and global capacity, or parks the request in `waiting_capacity`.
4. Creates a Gateway-owned **repository-backed** session (`sessions.create`, immutable baseline SHA).
5. Dispatches it to an allowed session-host node (`sessions.dispatch`, `deviceId` or `autoDevice`).
6. Sends **one** bounded task envelope as the first agent turn (`sessions.send`).
7. Tracks state, returns evidence or structured errors; cancels and reclaims idempotently.
8. Reconciles after Gateway restarts without ever duplicating a worker.
## What it never does
Decompose product tasks · pick business stages · judge code quality · choose models or
thinking levels · change agent configs · create agents · handle GitHub tokens ·
run shell · touch Docker · replace Git as source of truth · push/merge/delete branches ·
fall back to the control-plane host · patch OpenClaw binaries, `dist` or core DB.
## Tools
| Tool | Input | Effect |
|---|---|---|
| `placement_start` | task, role, repository `{url, baseline, taskBranch}`, manifest, correlation, `idempotencyKey` | Create or return one placement |
| `placement_status` | `placementId` | State, agent, session, device, timestamps, evidence summary |
| `placement_list` | task/role/state/initiative filters, bounded `limit`/`offset` | Paginated placement index (own records only) |
| `placement_cancel` | `placementId` | Idempotently stop a queued/active run; evidence stays for reclaim |
| `placement_reclaim` | `placementId` | Release the remote slot via `sessions.reclaim` (terminal placements only) |
Responses carry summaries, never tokens, environments or transcripts.
## Placement states
```text
requested → validating → waiting_capacity → creating_session → dispatching
→ starting_task → running → succeeded | failed | cancelled | blocked
→ reclaiming → reclaimed
Any ambiguity → recovery_required (operator decision, never a guess)
```
`waiting_capacity` is normal, not an error. `succeeded` means the worker turn
finished — not that the code was accepted. Reclaim never deletes branches,
checkpoints or repositories.
## Configuration
Strict (`additionalProperties: false` everywhere; unknown fields are errors, no secrets):
```json5
{
plugins: { entries: { "session-placement": { enabled: true, config: {
roles: {
coder: { agentIds: ["coder-a", "coder-b"], maxActivePerAgent: 1, target: "workers" },
qa: { agentIds: ["qa"], maxActivePerAgent: 1, target: "workers" }
},
targets: {
workers: { mode: "device", deviceIds: ["<stable-paired-device-id>"], requireSessionHost: true },
any: { mode: "auto" }
},
repositories: { allowedHosts: ["github.com"], allowedOwners: ["your-org"] },
limits: { maxActiveGlobal: 2, maxQueuedGlobal: 20, maxTaskSeconds: 7200, maxPromptBytes: 65536 },
recovery: { reconcileOnStartup: true, reconcileIntervalSeconds: 15, terminalRetentionHours: 72 }
} } } }
}
```
Growing the pool is a config edit, never a code change. `maxTaskSeconds` counts from
durable worker ownership (dispatch-active), queue waiting excluded. No auto-reclaim in
v1: freeing a slot is always an explicit `placement_reclaim`.
## Task and result envelopes
Worker input — first text turn, JSON between markers:
```text
BEGIN_TASK_ENVELOPE_V1
{"version":"TASK_ENVELOPE_V1","taskId":"…","role":"…","agentId":"…","attempt":1,
"repository":{"url":"…","baseline":"<40-hex-sha>","taskBranch":"task/…"},
"manifest":{"goal":"…","acceptanceCriteria":["…"],"ownedPaths":["…"],"forbiddenScope":["…"],"verification":["…"]}}
END_TASK_ENVELOPE_V1
```
Worker output — last message should contain:
```text
BEGIN_PLACEMENT_RESULT_V1
{"version":"PLACEMENT_RESULT_V1","outcome":"success|failed","commit":"…","branch":"…",
"changedPaths":["…"],"verification":["…"],"blockers":["…"],"residualRisks":["…"]}
END_PLACEMENT_RESULT_V1
```
## Install
Requires OpenClaw Gateway `>= 2026.9.5`.
```bash
openclaw plugins install github:Alexander-Green/openclaw-session-placement
openclaw plugins inspect session-placement # check load + trust facts
openclaw plugins doctor
```
Then allowlist the five `placement_*` tools for the coordinator agent only, e.g.
`agents.entries.lead.tools.alsoAllow`. Workers never receive placement tools, so a
worker can never spawn another worker.
## Develop
```bash
npm install
npm test # strict tsc build + node:test (unit + mocked integration)
```
Layout: `src/` (pure core: `config`, `schemas`, `state-machine`, `scheduler`,
`envelope`, `redaction`, `idempotency`, `errors`, `tools`; lifecycle: `service`,
`state-store`, `gateway-client`, `reconcile`; `index` wires Phase 3 in), `tests/unit`,
`tests/integration` (scripted mock Gateway: happy path, timeouts, restarts, stale
sessions, capacity, idempotency), `docs/` (discovery notes).
## Security model
- Code (prompts) is never a trust boundary: role, agent, node, repo owner and limits
are enforced in plugin code, not in the model prompt.
- Repository URLs are parsed structurally; credentials in URLs are rejected and redacted.
- Every externally visible string passes redaction (tokens, keys, auth headers, URL userinfo).
- Timeouts reconcile first; ambiguous outcomes become `recovery_required`, never a
duplicate worker or a blind RPC retry.
- Disabling the plugin fails closed: no remote worker starts.
## Status and compatibility
| Version | OpenClaw | State |
|---|---|---|
| 0.1.x | `2026.9.5` | Core + lifecycle + mocked tests; live smoke pending |
Plugin APIs used here are experimental upstream: pin the Gateway version, test every
host you declare, set compatibility ranges from tested versions only.
## License
MIT — see [LICENSE](LICENSE).
tools
Comments
Sign in to leave a comment