Skip to main content
If you followed the quickstart, the setup wizard already built your first agent. This tutorial builds a second one by hand, so you see every part the wizard hides: an agent group (the identity), a messaging group (the chat), and a wiring (the routing rule connecting them). We’ll use the CLI channel — it ships on trunk and needs zero platform credentials. You need a working install with the host service running, ncl on your PATH (or pnpm ncl from the checkout), and Docker up.
Building by hand is the way to understand the parts. Once you do, a template stamps a fully configured agent — persona, skills, MCP servers, and recurring tasks — in one command.
1

Create the agent group

An agent group is just a row in the central DB plus a folder under groups/ — both created in one step, as you’ll see.
ncl prints the inserted row, including the generated UUID. Save it — every later command takes --id:
Now check the filesystem: ls groups/ — the scout/ folder is already there. Create does more than insert the DB row: it runs initGroupFilesystem() inline, so the folder and the container-config row exist before any message arrives. Re-running create with the same --folder returns the existing row. If the directory already exists but no database row owns it, creation is refused rather than adopting its files: choose another folder or move the old directory first. What does not exist yet: the composed CLAUDE.md (generated at first container spawn), instructions.prepend.md (you create that file yourself when you give the agent standing instructions), and any session state. An agent that has never been messaged still costs nothing at runtime — no container runs for it.
2

Find the CLI messaging group

A messaging group is one chat on one platform, identified by a unique (channel_type, platform_id) pair. The CLI channel hardcodes its platform ID to local, and setup leaves the “Local CLI” messaging group in place, so it should already exist:
Note the id of the row with platform_id = local. If the list is empty (CLI-less install), create it exactly as the setup script does — unknown_sender_policy must be public because the terminal’s synthetic cli:local user holds no role:
3

Wire them together

A wiring tells the router which agent handles messages from which chat, and when it engages:
Two things matter here:
  • CLI wirings are always pattern mode. The CLI adapter declares pattern with a match-everything default, so omitting the engage flags would wire Scout to answer every message. An explicit --engage-mode mention is rejected outright — the CLI channel never produces mentions, and wiring validation refuses a mode that can never engage. The explicit pattern above is what scopes Scout to its name.
  • The pattern is a JavaScript regex tested against the message text. ^[Ss]cout\b engages Scout only when a message starts with its name. Don’t use inline flags like (?i) — JS regexes reject them, and an invalid pattern fails open (the agent responds to everything).
Routing is fan-out: every wiring on a chat is evaluated independently. Your setup-created agent is wired to this same chat with pattern . (match everything), so a message starting with “scout” engages both agents and you’ll get two replies. To give Scout exclusive ownership of its name, find the original wiring with ncl wirings list and narrow it: ncl wirings update --id <original-wiring-id> --engage-pattern '^(?![Ss]cout\b)'.
4

Talk to it

The message hits data/cli.sock, the router matches your pattern, creates a session, and spawns a container. First contact is a cold start — expect 30–60 seconds before the reply prints. Follow-ups to a warm container are much faster, and the session persists server-side, so context carries across invocations.If nothing comes back after the cold start, check what the router did with the message:
A no_agent_engaged drop row only appears when no wiring on the chat matched at all — in this tutorial’s setup a catch-all (or complementary) pattern always engages one agent, so if the table is empty, check whether the other agent answered instead: your message probably didn’t start with “scout”.
5

Inspect what was created

That first message triggered the first container spawn, which composed the one file creation left out:
CLAUDE.md is the composed instructions file the host regenerates on every spawn — don’t edit it. The agent’s editable standing instructions live in instructions.prepend.md, composed into the top of CLAUDE.md at every spawn. Groups created via ncl don’t have that file yet — create it to give Scout a personality and standing instructions (see customize an agent). Durable facts the agent accumulates over time live in its memory/ tree.The session — the runtime unit mapping this (agent, chat) pair to a container — is visible too:
Look for status = active and container_status = running. Current Docker container names start with ncl- and derive from the install and session IDs. List NanoClaw containers by label:
It stays warm for a while after the conversation until the host’s sweep kills it — the runner never exits just for being idle — and the host respawns it on the next message. To force a fresh one (with an image rebuild, e.g. after adding packages):

What you built

The four pieces the tutorial built: the CLI channel feeding the Local CLI messaging group, wired by the Scout pattern rule to the Scout agent group, whose session and container exist on demand The four pieces the tutorial built: the CLI channel feeding the Local CLI messaging group, wired by the Scout pattern rule to the Scout agent group, whose session and container exist on demand One agent identity, one chat, one routing rule between them — and a session/container pair that only exists because you sent a message. Every channel in NanoClaw works through these same four pieces; only the adapter changes. See the entity model for the full picture.

Next steps

Last modified on September 4, 2026