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
One agent = one folder under agents/<id>/. The folder is the agent's whole universe:
| Path | Purpose |
|---|---|
SOUL.md, IDENTITY.md, AGENTS.md, TOOLS.md, USER.md | Loaded into the system prompt in that order — openclaw's convention |
HEARTBEAT.md | Standing orders for periodic autonomous wake-ups (reply HEARTBEAT_OK when idle) |
memory/*.md | Persistent memory the agent reads and writes with file tools |
| everything else | The 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
| Rung | jsclaw surface |
|---|---|
| 1 Chatbot | webchat / chat.send |
| 2 Copilot | agent workspace + file tools |
| 3 Agent | the tool loop (bash, files, IPC) |
| 4 Autopilot | headless runs, jsclaw run |
| 5 Workflows | task scheduler: cron, interval, one-shot |
| 6 Assistant | heartbeat — drop a HEARTBEAT.md in the agent folder |
| 7 Multi-agent | agents are addressable everywhere; bindings route channels/peers to agents |
| 8 Orchestrator | one 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" }
| Mode | Behavior |
|---|---|
auto (default) | Containers when the engine responds; plain processes otherwise |
all | Every agent in a container — nanoclaw's posture, safest |
non-main | Main agent on the host, others contained |
off | Every 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.
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.
| Frontmatter | Surfacing |
|---|---|
| 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: true | Also 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
| Surface | Status |
|---|---|
Wire frames (req/res/event), connect handshake | ✅ openclaw 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
| Observation | What it means |
|---|---|
| "in" tokens dwarf your words | Every turn carries the harness: tool definitions, identity, memory, skills index. The webchat shows per-reply and session totals — watch them. |
| Sessions grow | History rides every turn. /reset is the valve — it works on every surface. |
| Cheap heartbeats | Set heartbeatModel to a small model: ~48 cycles/day is the biggest cost lever. |
| Allowlists are not optional | An open DM agent answers anyone and burns tokens. Channel config refuses to start without allowFrom unless you double opt in. |
| Unsandboxed + autonomous + untrusted input | Pick two. Exec approvals are on the roadmap; until then, that combination is yours to avoid. |