NAOMS Devlog

Building a sovereign, local-first memory & identity system โ€” in the open, honestly.

Let an AI Into the Group Chat Without Handing It the Room

In a group-owned conversation, everyone already in it gets a say. What they approve becomes a permission slip the assistant carries, and every single action it attempts is checked against that slip before it runs. Asking another person's calendar returns busy-or-free and nothing else.

Product Architect free August 14, 2026ยท10 min readยทsovereignty
TL;DR You can invite an AI assistant into a shared conversation, and in a group-owned conversation the people already in it get to object before it comes in. What you approve becomes a permission slip the assistant carries, checked before every single action it tries โ€” and the record of who let it in, who objected, and who removed it outlives the assistant.

There is a moment most products get wrong. Someone in a group chat wants an assistant in the room โ€” to summarise, to find a time everyone is free, to look something up. In most apps, adding it is one person's click. The assistant arrives with whatever access the platform gives assistants, everyone else finds out by watching it talk, and nobody can answer, a week later, who let it in.

We think that moment belongs to the people in the room. Not to the platform, not to whoever clicked first, and โ€” this is the part we had to fight for โ€” not to the group's own rulebook either.

Here is what happens now when you invite one.

flowchart TD
  A["You pick your own assistant
and the exact things it may do"] --> B["Everyone already in the room is asked
(in a group-owned conversation)"] B -->|"anyone objects"| C["It does not join.
A single objection stops it outright."] B -->|"someone hasn't answered yet"| H["It waits. Nothing is granted
while anyone is still silent."] B -->|"everyone consents"| D["A permission slip is issued
for exactly what was approved"] D --> E["Every action it tries is checked
against that slip before it runs"] E --> F["Asking about someone else returns
busy-or-free โ€” never the details"] D --> G["Who invited, who objected,
who removed โ€” kept, not forgotten"] E --> G

Admission is personal, and no group rule overrides it

Groups in NAOMS can be governed different ways โ€” one admin decides, everyone must agree, or a looser open arrangement. It seemed natural at first to let agent invitations inherit that rulebook. It was wrong, and the correction is the sharpest thing in this whole piece.

Admitting an assistant into a conversation should be a personal decision. Nobody should be voted into sharing a room with an AI. So in a conversation owned by a group, the invitation runs its own ceremony regardless of how that group is otherwise governed: each person can accept or object, and a single objection stops it outright, immediately. A loosely governed group does not get a looser bar for this.

And silence is not a yes. The assistant joins only once every member has actively consented. One objection stops it outright; one person who simply hasn't answered leaves the request sitting there, granting nothing. If the deadline passes without everyone having consented, the request is denied โ€” it does not quietly succeed.

That is the conservative direction, and it is the one we want. Someone asleep, offline, or not looking at their phone cannot be counted as agreeing to something they never saw. The cost is real and we accept it: one unreachable person can hold up an invitation. Being unable to add an assistant is an inconvenience. Being added to a room with one you never agreed to is not.

Before this landed, invitations in loosely governed groups were simply refused. That was a gap, not a feature, and it is closed.

Two lanes deliberately do not run this ceremony. The first is the admin-run conversation, where an administrator places the assistant directly. The second is more ordinary, and we should be equally plain about it: a conversation that isn't attached to a group at all has exactly one person in charge of it โ€” whoever started it โ€” and their invitation takes effect on its own, without asking anyone. If you want the room's say to be binding, the conversation needs to belong to a group. We think that default is wrong, and changing it is the next thing on this thread.

A refusal that tells you what actually happened

When an invitation is refused and we know why, you get the real reason โ€” the actual sentence explaining what blocked it, surfaced in the dialog you are standing in, rather than a shrug.

When we genuinely don't know โ€” the request never reached anyone, or came back with nothing to say โ€” you currently get a generic line instead, and we would rather admit that than let the paragraph above imply otherwise. The thing that should never happen is a confident-sounding message that invents a cause. Narrowing that second case is ongoing.

The same rule applies at the other end, and there it is unqualified: if you try to remove an assistant and you are not allowed to, the row stays and tells you why. It never animates away and pretends the assistant left when it is still sitting there.

The permission slip

When you invite an assistant, you also say what it may do โ€” which tools, which kinds of operation, which areas of your data. Everyone approving sees that list. The obvious failure here is the one we found in our own code: the list is displayed, everyone agrees to it, and then nothing downstream reads it. An assistant approved for one narrow lookup would quietly receive the entire standard toolkit, including web access and memory search.

Now, at the moment of approval, that approved list is written into a real cryptographic permission slip โ€” a capability token โ€” attenuated to exactly what the room agreed to and nothing more. It is issued to that specific assistant, it expires, and it can be revoked.

Worth saying what that token is not: it is not money, not a currency, not a thing with value. It buys nothing and is worth nothing to anyone but the assistant holding it. It is a signed statement of what this assistant may do, and nothing else.

It does two jobs.

First, it decides what the model is even told about. The tool list handed to the assistant is filtered through the slip, so a tool outside the grant is never described to it. There is nothing to be talked into using, because it does not know the door exists. An instruction buried in somebody's message cannot point at a capability that was never named.

Second โ€” and this is the real wall โ€” every action it proposes is checked before it runs. Not at the start of the session, not when the assistant was set up. Before each individual call: the slip's signature is re-verified, its expiry checked, the revocation list consulted, and the requested action tested against the grant. Only then does anything execute. If the model invents a tool name it was never offered, the check refuses it and the refusal is told back to the model so it can adjust rather than loop โ€” an honest "no", not a silent drop.

Every failure in that check is a refusal โ€” expired, revoked, unreadable, or simply not covered. Even an unexpected error denies.

There is one soft edge, and one gap that is bigger.

The soft edge: if the slip cannot be issued, or fails its own verification the instant after it is created, the assistant is activated without one and falls back to the approved-list check that still runs before every call. A keying problem narrows the assistant; it never widens it. It is logged loudly when it happens.

The gap is not a degradation โ€” it is history. Assistants that were already sitting in conversations before any of this landed have neither a permission slip nor a recorded approved list, because there was nothing to record one from. Nothing narrows them beyond their role's default toolkit, which is the very problem this section opened by describing. New invitations always carry a list. The old ones need removing and re-inviting, and we have not forced that yet โ€” so if you have an assistant that has been sitting in a conversation for a while, re-inviting it is what moves it behind the new wall.

If this idea appeals to you, it is the same family of thinking as authority that flows from you.

It has to ask about you. It cannot look.

The hardest case in a shared room is an assistant acting across several people at once. "Find a time we're all free" is the friendly version of "read four people's calendars".

It does not read them. When the assistant needs to know when people are free, it asks each person's device โ€” and what comes back is opaque busy-or-free blocks. No titles. No descriptions. No locations. The assistant learns that you are unavailable on Thursday morning; it does not learn why, and there is nothing in the reply for it to learn it from. The reply has room for a start time and an end time and nothing else.

We proved this the blunt way: one participant plants a calendar entry with a secret title, description and location on his own device, and the whole cross-person question then runs for real between two machines, signed at both ends โ€” and none of those three strings appear anywhere in what comes back. What that test does not yet cover is the assistant choosing to ask on its own; that half is checked separately.

When someone's device is reachable, it computes that answer itself and signs it. When it isn't, we fall back to whatever availability they published earlier โ€” computed on the asking side from patterns they already made public, never from their actual entries. Either way the result carries a flag that says whether it is complete, and that flag reads "complete" only when every person's device answered live with a signature that verified. If someone was offline, you are told the answer is partial rather than handed a confident-looking wrong one.

Permission to ask is a far smaller grant than permission to see. We think that should be the general rule for any AI acting on behalf of more than one person, and this is the first place we made it load-bearing.

The record outlives the assistant

An assistant in a room is a thing that happened, and you should be able to look it up.

There is a plain Agent participation history panel in the conversation's settings. Who invited it, when it joined, who objected and why, who revoked it โ€” in order, in readable language, as far as your own device has the record. It is a read-only view of events already written to the conversation's own permanent log, so nothing is reconstructed or decorated; when an event did not record who acted, it says so rather than guessing.

The property we care about most: it survives the assistant leaving. Remove the assistant and the member list is empty again โ€” but the history is still there, because it was never derived from the member list in the first place. Present-state surfaces forget; this record does not.

One thing we still owe that panel: if the record cannot be read at all, it currently just doesn't appear โ€” which looks identical to "no assistant was ever here." An audit surface is the last place that should stay quiet about its own failure, and it should say it couldn't read rather than show you nothing.

Alongside it, a joined assistant now appears in the member list with a clear marker and a working remove control that drives the proper revocation, rather than the control used to remove a person.

What we did not build

Someone who joins a conversation that already has an assistant can find it โ€” it is in the member list, in the conversation's settings โ€” and can decide whether to stay. But there is no interruption at the moment they join saying "an assistant is present here," and the in-thread indicator does not currently reflect an active assistant at all. So unless they open the settings, they may not realise. That is a real gap, and the more embarrassing half of it is a near-term fix rather than a design question.

They also cannot remove it โ€” but neither can most of the people who were already there. Today, removal is reserved for the person who invited the assistant and for the conversation's administrators. That is a narrower rule than "the room decides," it applies to original members and late arrivals alike, and whether it is the right line is a fair question we have not settled.

The summarising mode works on request only โ€” mention it and ask for a summary and you get one, with action items, and it stays silent through ordinary conversation. It does not summarise on a schedule and does not keep persistent meeting notes yet.

And we declined to build richer voting rules for loosely governed groups. Given the choice, we spent the effort on making admission personal wherever a group owns the conversation โ€” which, as the standalone case above shows, is not yet everywhere it should be.

Related: One calm home for the conversations you keep close ยท Default deny by design.


Written by AI agents from real project logs; owned and edited by Mujo.

โ† more in Product   home โœฆ   all โ†’