The handbook

Everything jsclaw does, compactly, as of v0.0.18. Start with getting started if you haven't run a gateway yet. Every feature below is tested and was verified against a live instance.

Agents & identity The autonomy ladder Sandboxing Channels Skills Slash commands Sessions MCP, both directions Plugins openclaw compatibility agent-micro Operations notes

Agents & identity

One agent = one folder under agents/<id>/. The folder is the agent's whole universe:

PathPurpose
SOUL.md, IDENTITY.md, AGENTS.md, TOOLS.md, USER.mdLoaded into the system prompt in that order — openclaw's convention
HEARTBEAT.mdStanding orders for periodic autonomous wake-ups (reply HEARTBEAT_OK when idle)
memory/*.mdPersistent memory the agent reads and writes with file tools
everything elseThe workspace — the only thing a sandboxed agent can see

Lineage: openclaw is the full platform, nanoclaw the secure WhatsApp assistant it inspired, jsclaw the engine underneath — deliberately reusing openclaw's shapes (identity files, wire protocol, config vocabulary) so the ecosystems interoperate.

The autonomy ladder

Rungjsclaw surface
1 Chatbotwebchat / chat.send
2 Copilotagent workspace + file tools
3 Agentthe tool loop (bash, files, IPC)
4 Autopilotheadless runs, jsclaw run
5 Workflowstask scheduler: cron, interval, one-shot
6 Assistantheartbeat — drop a HEARTBEAT.md in the agent folder
7 Multi-agentagents are addressable everywhere; bindings route channels/peers to agents
8 Orchestratorone jsclaw calling another's MCP messages_send is sub-agency (formal spawn tool: open)

Sandboxing

{ "sandboxMode": "non-main", "localRunner": "/path/to/agent-micro/runner.js" }
ModeBehavior
auto (default)Containers when the engine responds; plain processes otherwise
allEvery agent in a container — nanoclaw's posture, safest
non-mainMain agent on the host, others contained
offEvery agent as a plain process — openclaw's posture

Per-agent override: sandbox: true/false on the agent config. Unsandboxed agents are spawned from localRunner with the workspace and IPC dirs as env vars.

Unsandboxed agents have no isolation. Their bash and file tools run as you. The agent folder is a security boundary only when there's a container around it. Keep autonomous agents and anything touching untrusted input sandboxed.

Channels

The gateway hosts chat surfaces from config — channel blocks share openclaw's universal fields (allowFrom, dmPolicy, enabled); routing to agents lives in bindings, never in the channel:

{
  "channels": {
    "nostr":    { "privateKey": "${NOSTR_PRIVATE_KEY}", "allowFrom": ["npub1...you"],
                  "relays": ["wss://relay.damus.io", "wss://nos.lol"] },
    "telegram": { "botToken": "${TELEGRAM_BOT_TOKEN}", "allowFrom": ["123456789", "@you"] }
  },
  "bindings": [{ "match": { "channel": "nostr" }, "agentId": "research" }]
}

Nostr is built in and zero-dep (NIP-04 encrypted DMs — message the npub from the boot banner; no platform, no bot token). Telegram uses grammy (jsclaw's one dependency). allowFrom is required: an open DM agent answers anyone and burns tokens (dmPolicy: "open" + allowFrom: ["*"] to accept that). One session per (channel, peer).

Skills

openclaw / Anthropic SKILL.md format — folders with resources, or flat files. Install from a path: jsclaw skill install ./my-skill.

FrontmatterSurfacing
no trigger (the upstream norm)Description-driven: the agent sees a compact index (name + description + path) and reads the body on demand — 58 installed skills cost index lines, not 58 bodies per prompt
trigger: "deploy|/regex/i|attachment:image"jsclaw extension: host-side match injects the body into that prompt
user-invocable: trueAlso becomes /skillname — force-injects the body with your arguments

Sandboxed agents get the skills dir as a read-only mount; paths in the index always resolve from inside the run. Real openclaw skills parse 58/58; their metadata.openclaw.requires (binary deps) is not yet honored — pure-knowledge skills work fully today.

Slash commands

Host-handled before the agent sees the message — deterministic, instant, zero tokens, identical on webchat, TUI, Nostr, and Telegram: /reset (+/new), /status, /skills, /help (+/commands), plus user-invocable skills and plugin commands. commands.list serves openclaw-shaped entries so their clients render them. Unknown commands answer with /help guidance instead of reaching the agent.

Sessions

openclaw's two-layer model: a persistent index (data/sessions.json: key → {sessionId, agentId, label?, model?, updatedAt}) over the runners' transcript files. Keys: main (webchat/TUI default), <channel>:<peer>. Sessions survive gateway restarts; /reset mints a fresh conversation under the same key.

Wire methods: sessions.list, sessions.reset (reason: "reset"|"new"), sessions.patch (label, model — null clears). A patched model flows into runs: switch one conversation to a cheaper model without touching config.

MCP, both directions

The gateway is an MCP server

POST /mcp — JSON-RPC 2.0, Streamable HTTP (2025-03-26), stateless, auth via Authorization: Bearer <gateway token>. Tools speak openclaw's bridge vocabulary: conversations_list, messages_send (send into a session key, get the reply), messages_read, agents_list, skills_list. Point Claude Desktop, Claude Code, or any MCP host at it and your agents become tools.

Agents are MCP clients

agent-micro connects to HTTP MCP servers from config; discovered tools join the loop as mcp__<server>__<tool>:

{ "mcp": { "servers": {
  "pod": { "url": "https://your-jss/mcp", "headers": { "authorization": "Bearer ..." } }
} } }

Division of labor: plugins extend the gateway; MCP extends the agent. The default (Claude Code) runner brings its own MCP client; the same config passes through.

Plugins

A plugin is a directory with jsclaw.plugin.json ({id, version, entry}) whose entry exports activate(api). The api registers into the four host seams: registerChannel, registerCommand, registerGatewayMethod, registerMcpTool. Failing plugins warn and skip.

{ "plugins": {
  "load": { "paths": ["./my-plugin"] },
  "deny": [], "allow": null,
  "entries": { "my-plugin": { "enabled": true, "config": { "key": "value" } } }
} }

Policy is openclaw's: deny > allow > per-entry enabled. See examples/plugins/ping for the full contract in 25 lines.

openclaw compatibility

SurfaceStatus
Wire frames (req/res/event), connect handshakeopenclaw tui connects to a jsclaw gateway
chat.send / chat.history / chat events✅ sessionKey addressing, openclaw event shapes
sessions.list/reset/patch✅ (patch: label + model; exotic fields stubbed)
commands.list, agents.list, models.list✅ real entries (models: configured model only)
Channel config vocabulary, bindings, SKILL.md, MCP bridge tools, plugin config shape✅ field-for-field where concepts map
chat.abort, devices/pairing, nodes, voice, canvas✗ stubbed or not attempted

agent-micro

The readable runner: ~1,000 lines, zero dependencies, AGPL, Anthropic-compatible API over fetch (works with GLM and Kimi). Speaks the full container contract — stdin ContainerInput, sentinel-delimited stdout, identity files, memory, IPC tools, sessions, token usage, the skills index, and the MCP client. Use it sandboxed as the container image, or unsandboxed via localRunner for a no-Docker host.

Operations notes

ObservationWhat it means
"in" tokens dwarf your wordsEvery turn carries the harness: tool definitions, identity, memory, skills index. The webchat shows per-reply and session totals — watch them.
Sessions growHistory rides every turn. /reset is the valve — it works on every surface.
Cheap heartbeatsSet heartbeatModel to a small model: ~48 cycles/day is the biggest cost lever.
Allowlists are not optionalAn open DM agent answers anyone and burns tokens. Channel config refuses to start without allowFrom unless you double opt in.
Unsandboxed + autonomous + untrusted inputPick two. Exec approvals are on the roadmap; until then, that combination is yours to avoid.