Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Channels

The Channel is Tinytown’s message transport layer. It’s a thin wrapper around Redis that provides queues, pub/sub, and state storage.

Why Redis?

Redis is perfect for agent orchestration:

FeatureBenefit
Unix socketsSub-millisecond latency
ListsPerfect for message queues
BLPOPEfficient blocking receive
Pub/SubBroadcast to all agents
PersistenceSurvives crashes
SimpleNo complex setup

Channel Operations

Send a Message

#![allow(unused)]
fn main() {
channel.send(&message).await?;
}

Messages go to the recipient’s inbox (tt:<town>:inbox:<agent-id>).

Priority handling:

  • Urgent / HighLPUSH (front of queue)
  • Normal / LowRPUSH (back of queue)

Receive a Message

#![allow(unused)]
fn main() {
// Blocking (waits up to timeout)
let msg = channel.receive(agent_id, Duration::from_secs(30)).await?;

// Non-blocking
let msg = channel.try_receive(agent_id).await?;
}

Uses BLPOP for efficient waiting without polling.

Check Inbox Length

#![allow(unused)]
fn main() {
let pending = channel.inbox_len(agent_id).await?;
println!("{} messages waiting", pending);
}

Broadcast

#![allow(unused)]
fn main() {
channel.broadcast(&message).await?;
}

Uses Redis Pub/Sub (PUBLISH tt:broadcast).

State Storage

The channel also stores agent and task state:

Agent State

#![allow(unused)]
fn main() {
// Store
channel.set_agent_state(&agent).await?;

// Retrieve
let agent = channel.get_agent_state(agent_id).await?;
}

Stored at: tt:<town>:agent:<uuid>

Task State

#![allow(unused)]
fn main() {
// Store
channel.set_task(&task).await?;

// Retrieve
let task = channel.get_task(task_id).await?;
}

Stored at: tt:<town>:task:<uuid>

Redis Key Patterns

Keys are town-isolated to allow multiple towns to share the same Redis instance:

PatternTypePurpose
tt:<town>:inbox:<uuid>ListAgent message queue
tt:<town>:agent:<uuid>StringAgent state (JSON)
tt:<town>:task:<uuid>StringTask state (JSON)
tt:broadcastPub/SubBroadcast channel

See tt migrate for upgrading from older key formats.

Direct Redis Access

Sometimes you want to query Redis directly:

# Connect to town's Redis
redis-cli -s ./redis.sock

# List all agent inboxes for your town
KEYS tt:<town_name>:inbox:*

# Check inbox length
LLEN tt:<town_name>:inbox:550e8400-e29b-41d4-a716-446655440000

# View agent state
GET tt:<town_name>:agent:550e8400-e29b-41d4-a716-446655440000

# Monitor all messages
MONITOR

Performance

Unix socket performance is excellent:

OperationLatency
Send message~0.1ms
Receive (cached)~0.1ms
State get/set~0.1ms
TCP equivalent~1-2ms

For local development, this means near-instant coordination.

Persistence

By default, Redis runs in-memory only. For durability:

Option 1: RDB Snapshots

redis-cli -s ./redis.sock CONFIG SET save "60 1"

Saves every 60 seconds if at least 1 key changed.

Option 2: AOF (Append Only File)

redis-cli -s ./redis.sock CONFIG SET appendonly yes

Logs every write for full durability.

Creating a Channel

Usually you don’t create channels directly—the Town does it:

#![allow(unused)]
fn main() {
// Get channel from town
let channel = town.channel();

// Or create manually (advanced)
use redis::aio::ConnectionManager;
let client = redis::Client::open("unix:///path/to/redis.sock")?;
let conn = ConnectionManager::new(client).await?;
let channel = Channel::new(conn);
}