← Back to Plugins
Tools

Session Placement

Alexander-Green By Alexander-Green 👁 80 views ▲ 0 votes

OpenClaw plugin: safe remote task-session placement on session-host nodes

GitHub

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

Loading comments...