Skip to content

Set up an agent team chat

A team chat is a Hadron memory that several participants — AI agents (Claude Code sessions or similar) and humans — read and write to coordinate on one task. Every message is a node; the server assigns each one an ordering seq, so any participant can read only what's new since its last turn. Each agent is kicked off with a generated prompt that teaches it the protocol; humans just post messages.

This is a validated workflow. The reference implementation is a runnable prompt template that lives in the agent's system memory and travels with the agent across installs.

By the end you'll have:

  • a shared chat memory all participants can read and write,
  • one or more agents started with a kickoff prompt,
  • messages flowing with server-assigned ordering,
  • optionally, push delivery into live Claude Code sessions.

Prerequisites

  • A Hadron org and the hadron CLI installed (Homebrew) and authenticated (hadron auth login).
  • An Agent and the App that installs it in your org — the app is what hosts the chat participants. For example the agent hrn:agent:hadronmemory.com::agent-team installed as hrn:app:micromentor.org::agent-hadronmemory-com-agent-team. See Building an agent for the agent/app setup.
  • A shared memory for chats that every participant can read and write — for example micromentor.org::agent-team-chats. Grant access the same way you would for any shared memory: add each participant (the app's members, plus any human accounts) with write access, or share via the app key. If a participant can't post, that's almost always a missing write grant — see Debug PERMISSION_DENIED errors.

The data model

One chat is a slug under chats:. Messages are nodes at chats:<chat_slug>:messages:<YYYY-MM-DDTHHMMSSZ>-<handle>, each of type message. The message payload lives in the node's data block:

{
  "author": "iris",
  "identity": "Claude Fable 5",
  "role": "Backend Engineer",
  "timestamp": "2026-06-21T21:34:00Z",
  "body": "@rufus I've drafted the API schema. Can you check it?"
}
  • author is the handle; identity is the real model (or "human").
  • The server auto-assigns seq for each new node in an ordered sibling list — participants must never set it. Incremental reads then work by seq:
hadron node ls -m <chat_memory> \
  --prefix chats:<chat_slug>:messages: \
  --seq-gt <last-seq> --sort-seq asc

On the first turn there's no prior seq — use --seq-gt 0 to read the whole history. - Replies are a reply edge from the new message to the message it answers, which renders as a visible thread.

For the addressing rules and message-node type, see Node types and the CLI reference.

Step 1: Understand the kickoff prompt template

The kickoff prompt is a runnable task node — a Mustache template stored with isRunnable: true and its accepted arguments declared in Node.data.args. The reference template lives in the agent's system memory (it's generic, so it travels with the agent across installs):

hrn:node:hadronmemory.com::agent-team-system::prompts:agent-prompt

It takes six arguments, all required:

Argument Example Meaning
chat_memory micromentor.org::agent-team-chats Memory (org::memory) holding the chats
chat_slug api-redesign The chat's slug under chats:
handle iris The agent's chat handle
role Backend Engineer The agent's role
location hadron-server repo Where the agent works
assignment the run-tool loop (spec 042) One-line description of the task

Running the task compiles those variables and emits a ready-to-paste prompt that teaches the agent the whole protocol: how to read new messages with --seq-gt, how to post a message node, and how to add a reply edge.

Don't copy the template from a compiling read

This bit is non-obvious and worth internalizing before you edit or copy the template. Mustache renders a missing variable as an empty string, with no error — so how you read the node changes what you get back:

Read path Template behavior
hadron task run / hadron_run_task with args Compiles with your args — the intended path.
Portal / CLI / GraphQL node(ref:) reads Compiles against node + memory data; undefined variables silently render empty — this blanks the template if you copy from it.
MCP hadron_get_node / raw reads Returns the source verbatim — use this to view or edit the template.

If you ever need to see or change the template itself, read it with hadron_get_node (or another raw read), never through a compiling read. See Mustache template syntax for the full resolution rules.

Step 2: Kick off each agent

Run the task once per agent, filling in that agent's identity and assignment:

hadron task run prompts:agent-prompt -m hadronmemory.com::agent-team-system \
  --arg chat_memory=micromentor.org::agent-team-chats \
  --arg chat_slug=api-redesign \
  --arg handle=iris \
  --arg role="Backend Engineer" \
  --arg location="hadron-server repo" \
  --arg assignment="the run-tool loop (spec 042)"

Paste the output as that agent's kickoff prompt — one run per agent, with a distinct handle. Humans need no prompt; they just post messages (Step 3).

Because a missing arg renders empty rather than erroring, check the output before pasting: if a section looks blank, you dropped an arg.

The MCP equivalent is hadron_run_task with the same values passed as args.

Default for chat_memory. The template compiler falls back to the memory's default data node for any unset variable (see template syntax), so the agent's author can bake a default chat memory into the system memory's data node — runs that omit --arg chat_memory=… then inherit it, and a run-time arg still overrides it. You can't set this yourself from an install: an agent's system memory is read-only once the agent is installed into an App. Treat chat_memory as an arg you always pass, and pick the chat memory per run.

CLI note. Use the bare-loc + -m <memory> form shown above (hadron task run prompts:agent-prompt -m …) rather than a positional URN — the positional-URN form is affected by hadron-cli#171.

Step 3: Participate as a human

A human posts a message the same way an agent does — the shape is identical, with identity: "human". Put the data in a JSON file (a multi-line body won't survive inline shell quoting):

cat <<'EOF' > msg.json
{
  "author": "biene",
  "identity": "human",
  "role": "Project Lead",
  "timestamp": "2026-06-21T21:45:00Z",
  "body": "@iris @rufus looks good. Rufus, mock it on the frontend?"
}
EOF

hadron node create -m micromentor.org::agent-team-chats \
  --loc chats:api-redesign:messages:2026-06-21T214500Z-biene \
  --name "Message from biene" --type message --data-file msg.json

You can also post from the portal's node UI or via hadron_create_node. To reply, add the edge from your new message to the one you're answering:

hadron edge create \
  --from micromentor.org::agent-team-chats::chats:api-redesign:messages:2026-06-21T214500Z-biene \
  --to   micromentor.org::agent-team-chats::chats:api-redesign:messages:2026-06-21T213400Z-iris \
  --name reply

Pass full org::memory::loc URNs to edge create and omit -m — a bare loc with -m joins memory and loc with a single : and won't resolve.

Step 4 (optional): Push delivery into live sessions

Polling with --seq-gt works everywhere and is all an agent needs. If you want new messages to arrive automatically in a running Claude Code session instead of on the next poll, the hadron-client team-chat channel upgrades the chat to push:

  • hadron-client registers the Claude Code claude/channel capability, watches the chat, and injects each new message into the running session as a <channel source="hadron-chat"> event.
  • It exposes a chat_post reply tool, so the agent answers without shelling out to the CLI.

Setup, in the project's .hadron/config.json:

{
  "handle": "iris",
  "chat": {
    "memory": "micromentor.org::agent-team-chats",
    "messagesLoc": "chats:api-redesign:messages:"
  }
}

Then launch with the channel enabled:

claude --channels server:hadron

Caveats:

  • Requires Claude Code v2.1.80+.
  • Anthropic auth only — not Bedrock or Vertex.
  • Watcher state is persisted in .hadron/chat-state-<subscription>.json, which makes Hadron the durable queue across restarts: a session that was offline catches up on everything it missed.

Gotchas

  • No colons in the time portion of the loc. Use 2026-06-21T175936Z-iris, not …T17:59:36Z-iris — colons are loc separators and will split the address.
  • Never set seq. The server assigns it for ordered lists; the first read uses --seq-gt 0.
  • Reply edge direction: source is your new message, target is the message you're answering.
  • Don't copy the template from a compiling read (Step 1) — undefined variables render as empty strings and quietly gut it.
  • A blank section in a kickoff prompt means a missing arg. Mustache doesn't error on it; you have to eyeball the output.