I add my bot to a group chat with two friends. It answers their questions without me having to respond myself, and it knows when to stay quiet.
Mockup 00The platonic ideal
What an "awesome texting experience" feels like from the user's side, and the abstract architecture that must be behind it. Implementation choices (tmux vs claude -c -p, one-box vs two-box) are deferred to the concrete-option mockups — this page is what it must feel like, not how we build it.
A friend adds the bot to a group text. Over the next hour: it stays quiet during chatter. When @mentioned with a public-friendly question, it replies usefully in <15 s. When someone asks "is Jacob free Tuesday?", the bot says "let me check", DMs Jacob privately with the question + proposed response, and only answers the group once Jacob approves. No one sees private information Jacob didn't explicitly approve.
1User stories
I ask "is Jacob free Tuesday?" in the group. I get a real answer within a couple of minutes, not "I can't access that."
When the bot needs my private info to answer in a group, it DMs me with the exact response it wants to send, and I approve or tweak before anything leaves my DMs.
I can tell the bot "louder" or "quieter" per chat, and I can see and shape what it's allowed to know about me in each chat.
When I react ⭐ to a useful bot message, it saves it to Jacob's notes on our behalf. ❌ retracts a bad bot answer.
It feels native — threads, reactions, typing indicator, no markdown walls on iMessage.
2Scene A — bot respects attention
Default in a new group chat: mentions-only. Bot stays silent through normal chatter. Speaks when @mentioned, replied-to, or slash-commanded.
3Scene B — the consent loop (the banger)
Group members ask something that requires data the group's knowledge manifest doesn't cover. Bot pauses, DMs Jacob out-of-band with the proposed response, and only replies in-group after explicit approval.
What the audit row looks like
DisclosureEvent {
id: "de_7x9k2",
source_chat: "group:tuesday-plans",
source_message: "msg_4801",
asker: "+1-415-555-alice",
data_accessed: ["calendar:2026-04-28T19:00/21:00"],
proposed_reply: "Jacob's open 7-9pm Tuesday. Works for him!",
approval_mode: "approve-exact",
approver: "jacob",
approved_at: "2026-04-23T20:01:12Z",
final_reply: "...(same as proposed)...",
grant_ttl: "single-use"
}
4Scene C — manifest dialog
Bot will disclose its own boundaries when asked. This is both a feature and a trust signal.
5Scene D — reactions trigger behavior
Reactions are first-class input. Bot reacts to acknowledge (🤔 thinking, ✅ done). Reactions on the bot trigger save/retract/remember.
6Architecture — abstract, channel- and executor-agnostic
The minimum set of components any concrete option must instantiate. The platonic architecture only names roles, not machines or processes. See the two concrete mockups for one-box and two-box materializations of these roles.
Why "out-of-band"? The approval question from the bot goes to Jacob's private DM with the bot — not back into the group where Alice asked. Same phone, same app, same DM thread Jacob already uses for 1:1 chats; distinct conversation from the group. The group only sees "let me check" and the eventual answer (or "I don't have access to that"). Alice cannot observe that an approval loop happened.
7Flow of a consent-loop turn
- Inbound. Alice's message arrives on the Channel adapter (Linq or OpenChat webhook). HMAC verified, dedup'd, logged.
- Attention gate. Contains
@picortexand a question addressed to Jacob's calendar. Gate passes. - Executor starts turn. Reads the chat's manifest —
calendarnot in scope. - Pause → consent broker. Executor yields; consent broker takes over. Bot emits "let me check" to the group; typing indicator on.
- Out-of-band DM to Jacob. Consent broker formats context + proposed response; sends via the same Channel adapter to Jacob's DM.
- Jacob approves. Reply "y" (or tap approve). Broker validates, writes
DisclosureEvent, resumes the original turn with scope expanded. - Final reply. Executor finishes and emits the approved message into the group. Audit row closes with
final_reply. - Timeout path. If Jacob doesn't respond in 10 min, broker closes the loop: group gets "I need to check on that offline", audit row marks
approval_mode: "timeout". No indefinite waits.
8What's explicitly abstract here
- Nothing says one process or two, one box or many, tmux or not.
- Knowledge layer is drawn as one role but in reality is
{ per-chat manifest store, noos graph, transcript, files }— any of which can live anywhere. - The Channel adapter hides Linq vs OpenChat specifics — both normalize to the same event vocabulary.
- The Executor is the most replaceable piece:
claude -c -pper turn, a tmux REPL, OpenClaw, or something else — all satisfy this diagram.
9What the concrete options change
Bot server and the Claude box live on different machines. Executor is an SSH call that runs claude --session-id <chat> -p. Knowledge layer is HTTP to noos on a third reused box. See mockup → (or the reference doc for what Piyush literally shipped)
Everything in one process, no persistent workspace. Executor rebuilds transcript per turn and calls claude --print. Knowledge layer is HTTP to noos. See mockup →