Per-thread memory: how humanoids keep twenty conversations straight
One agent, twenty simultaneous conversations, zero bleed. How rysh humanoids scope memory by channel and thread — and why it's bounded.
Here's a failure mode every chatbot builder eventually ships: customer A asks about a refund, customer B asks about an API key, and the bot tells customer B about customer A's refund. One brain, one memory, many conversations — and the memory leaks sideways.
The fix sounds obvious — keep a context per conversation — but most DIY bot stacks bolt it on late, per channel, three different ways. In rysh, where a humanoid is one agent answering on Slack, email, and website chat simultaneously, conversation scoping couldn't be an afterthought. It's structural.
The key: channel + thread
Every inbound message a humanoid receives is stamped with where it came from: the channel type and the thread it belongs to — a Slack thread, an email conversation, a website chat session. The humanoid keeps a separate conversation context for each channelType/threadID pair.
So when support-bot is fielding:
- a Slack thread in
#supportabout a failed staging deploy, - a second Slack thread about SSO configuration,
- an email chain about an invoice,
- and two website-chat visitors asking about pricing,
…that's five independent contexts in one agent. The deploy thread's history never appears in the invoice reply. The pricing visitor doesn't hear about SSO. One persona, one knowledge base, many hermetically separate conversations — which is exactly how a good human support engineer works, minus the tab exhaustion.
There's nothing to configure. Spawn the humanoid from its markdown skill file, connect channels, done:
##humanoid spawn support-bot.md
##humanoid channel start support-bot slack
##humanoid channel start support-bot email
##humanoid channel start support-bot chatbot
Threading comes with the architecture, not with extra plumbing you write.
Memory is bounded on purpose: 20 turns, 24 hours
Each per-thread context keeps a rolling 20-turn history with a 24-hour TTL. Both numbers are doing real work, and I want to defend them, because "bounded memory" sounds like a limitation until you've operated the unbounded kind.
20 turns is a context budget you can reason about. Every stored turn is tokens you pay for and latency you wait on, on every subsequent reply in that thread. Unbounded histories mean your longest-running conversation is your most expensive and slowest — precisely the escalated, grumpy one where you want the agent sharp. A rolling window keeps the recent exchange fully in view and keeps cost per reply roughly flat.
24 hours is a privacy and staleness policy, not a shrug. A support conversation that went quiet yesterday shouldn't silently shape an answer today — products change, moods change, and the person who wrote "just cancel everything" at midnight deserves a fresh start at noon. Expired contexts are dropped, not archived into some shadow profile. What the agent should remember durably — product facts, runbook commands, policies — belongs in the skill file, in git, where it's reviewed and versioned. Conversation memory is for conversations.
That split — durable knowledge in the file, ephemeral memory in the thread — is the design decision this whole feature hangs on. Wrong answers get fixed by editing markdown, not by hoping the right correction happens to be inside some thread's history.
Why the thread, not the user
A subtle choice worth making explicit: contexts key on the thread, not on a cross-channel user identity. If Ayşe asks a question on website chat and later emails, those are two contexts. Deliberately.
Merging identities across channels means building a profile of people who never asked for one — matching emails to chat sessions to Slack handles, and carrying yesterday's sentiment into today's unrelated question. That's a correlation engine, and it changes what your bot is. Thread-scoped memory keeps the agent useful (full recall within the conversation you're actually having) without turning it into surveillance infrastructure. If a conversation needs history from elsewhere, a human can bring it — the humanoid runs in your terminal, where you can talk to it directly:
@support-bot the visitor on chat asking about invoices is the same
customer from this morning's email thread — context: order #4417
You, not a heuristic identity matcher, decide when contexts connect.
Watching it work
Because a humanoid lives in your rysh session — not in a vendor cloud — you can observe the whole thing next to your shell. ##humanoid channels support-bot shows per-channel connection status; register its output into a pane and the conversations stream where you work. In human (draft-and-confirm) governance mode you see each per-thread draft before it goes out — live today for email and website chat — which doubles as the best possible demo that threads aren't bleeding: twenty conversations, each draft anchored to its own history.
And the hard stops stay one command away: ##humanoid channel stop support-bot chatbot for one channel, @@support-bot deactivate for the lot.
Honest scope
- Live channels today: Slack, email (IMAP IDLE/SMTP), and website chat. WhatsApp and phone are roadmap, not live.
- The 20-turn / 24-hour bounds are the current shipped defaults; per-humanoid tuning is the kind of thing design-partner feedback will shape.
- rysh runs on Claude, your own key, provider layer pluggable. Self-hostable, not open source. Private beta.
Memory scoped to the conversation, knowledge scoped to the file, identity scoped to nothing — that's the shape that keeps twenty simultaneous conversations straight, and keeps the agent something you'd let near your customers.
Running a support bot that occasionally free-associates across customers? We should talk — rysh is onboarding design partners.
More in this series: One SKILL.md, three channels · governance ai|human