Skip to main content
In Tutorial 03, you saw how to peek at agent output in polecat sessions, attach to crew sessions, and nudge them with messages. All of that was you talking to agents. This tutorial covers how agents talk to each other. We’ll pick up where Tutorial 03 left off. You should have my-city running with my-project rigged, and agents for mayor and reviewer.

Agents talking to each other

Up to this point, you’ve been managing sessions one at a time — creating them on demand for polecats, keeping with alive as crew with named sessions. But a city isn’t a collection of independent agents working in isolation. It’s a system of agents that can talk to each other. The agents in your city don’t call each other directly. There are no function calls between them, no shared memory, no direct references. Each session is its own process with its own terminal, its own conversation history, and its own provider. The mayor doesn’t have a handle to a polecat or vice versa. However, they can still coordinate with each other via mail and slung work. Both are indirect — the sender doesn’t need to know which session receives the message or which instance picks up the task. Gas City handles the routing. This indirection is deliberate. Because agents don’t hold references to each other, they can run, go idle, restart, and scale independently. The mayor can dispatch work to my-project/reviewer without knowing whether there’s one reviewer session or five for that rig, whether it’s on Claude or Codex, or whether it’s currently active or idle. The work and the messages persist in the store. The sessions come and go. Mail is the primary way agents talk to each other. Slung work — gc sling — is how they delegate tasks. Let’s look at both.

Mail

Mail creates a persistent, tracked message that the recipient picks up on its next turn. Unlike nudge (which is ephemeral terminal input), mail survives crashes, has a subject line, and stays unread until the agent processes it. Mail itself does not wake the recipient. Send mail to the mayor:
~/my-city
$ gc mail send mayor -s "Review needed" -m "Please look at the auth module changes in my-project"
Sent message mc-wisp-8t8 to mayor
gc mail send takes the recipient as a positional argument and the subject/body via -s/-m flags. (You can also pass just <to> <body> with no subject.) Check for unread mail:
~/my-city
$ gc mail check mayor
1 unread message(s) for mayor
See the inbox:
~/my-city
$ gc mail inbox mayor
ID           FROM   SUBJECT        BODY
mc-wisp-8t8  human  Review needed  Please look at the auth module changes in my-project
gc mail inbox defaults to unread messages, so there’s no STATE column — everything listed is unread by definition. If you want to see the mayor react right away in peek or logs, give it a turn:
~/my-city
$ gc session nudge mayor "Check mail and hook status, then act accordingly"
Nudged mayor
The mayor doesn’t have to manually check its inbox. Gas City installs provider hooks that surface unread mail automatically — on each turn, a hook runs gc mail check --inject, and if there’s unread mail, it appears as a system reminder in the agent’s context. The agent sees its mail without doing anything. That nudge does not deliver the mail by itself — it just wakes the mayor so a new turn starts. When the mayor wakes up or starts a new turn, hooks deliver any pending mail, and the nudge tells it to act on what it finds.

Slinging beads to coordinate agents

Here’s what coordination looks like in practice. Once the mayor takes a turn, it reads the mail message you sent. It decides the reviewer should handle it, so it slings the work:
~/my-city
$ gc session peek mayor --lines 6
[mayor] Got mail: "Review needed" — auth module changes in my-project
[mayor] Routing to my-project/reviewer...
[mayor] Running: gc sling my-project/reviewer "Review the auth module changes"
(The above is illustrative — peek returns the actual terminal contents of the session, so you’ll see whatever the agent has rendered, not Gas City–formatted lines.) The mayor didn’t talk to the reviewer directly. It slung a bead to the my-project/reviewer agent template, and Gas City figured out which session picks it up. If the reviewer was asleep, Gas City woke it. If there were multiple reviewer sessions for that rig, Gas City routed the work to an available one. The mayor doesn’t know or care about any of that — it describes the work and slings it. This is the pattern that scales. A human sends mail to the mayor. The mayor reads it, plans the work, and slings tasks to agents. Those agents do the work and close their beads. Everyone communicates through the store, not through direct connections. Sessions come and go; the work persists.

Hooks

Hooks are what make all of this work behind the scenes. Without hooks, a session is just a bare provider process — Claude running in a terminal, with no awareness of Gas City. Hooks wire the provider’s event system into Gas City so agents can receive mail, pick up slung work, and drain queued nudges automatically. The tutorial template sets hooks at the workspace level, so all your agents already have them:
[workspace]
install_agent_hooks = ["claude"]
You can also set them per agent:
# agents/mayor/agent.toml
install_agent_hooks = ["claude"]
Agent-local overrides like this live in agents/<name>/agent.toml. When a session starts, Gas City installs hook configuration files that the provider reads. For Claude, this means a hooks/claude.json file that fires Gas City commands at key moments — session start, before each turn, on shutdown. Those commands deliver mail, drain nudges, and surface pending work. Without hooks, you’d have to manually tell each agent to run gc mail check and gc prime. With hooks, it happens on every turn.

What’s next

You’ve seen the two coordination mechanisms — mail for messages and slung beads for work — and the hook infrastructure that wires it all together. From here:
  • Formulas — multi-step workflow templates with dependencies and variables
  • Beads — the work tracking system underneath it all
Last modified on April 16, 2026