Every user gets a private agent with private memory, separate from all other users. Each agent lives in its own group folder. Routes from that user's JID go to their folder. The parent group's grant rules automatically constrain what the user's agent can do.
The parent agent calls spawn_group when it decides to give a user their own agent:
spawn_group({
"name": "alice",
"parent": "main"
})
This creates groups/main/alice/ with a copy of the parent's skills and a blank CLAUDE.md. The new group is at tier 1 — depth 2 from groups/ — and inherits the parent's grants, narrowed to tier 1 defaults.
register_group({
"chat_jid": "telegram:123456789",
"group_folder": "main/alice"
})
This inserts a row into registered_groups. From this point on, messages from telegram:123456789 route to groups/main/alice/.
You can do both steps from the parent agent in one conversation turn. The parent agent calls spawn_group, then immediately calls register_group with the new folder path.
groups/
main/ # tier 0, full access
CLAUDE.md
diary/
facts.md
alice/ # tier 1, narrowed grants
CLAUDE.md # alice's private instructions
diary/ # alice's private diary
facts.md # alice's private facts
users/
telegram:123456789.md # alice's own profile
bob/
...
eve/
...
Each user's folder is completely independent. Alice's diary does not share entries with Bob's. Alice's agent does not have access to the main group's diary.
After registration, the registered_groups table contains:
chat_jid group_folder created_at
telegram:-100111222333 main 2024-01-01 (group chat → main)
telegram:123456789 main/alice 2024-03-15 (DM → alice's group)
telegram:987654321 main/bob 2024-03-16 (DM → bob's group)
The gateway resolves routes using longest-prefix match on the JID. A direct message to Alice always routes to her group. If Alice is also a member of the group chat, messages in the group chat route to main — a different agent with shared context.
Alice's agent is at tier 1. It inherits the parent's grants, narrowed by NarrowRules. If main has full access (*), Alice gets tier 1 defaults:
send_message
send_reply
spawn_group # Alice can create her own subgroups
!delegate_to_child # but not delegate to them by default
The parent can further restrict Alice's group by adding a .grants file to groups/main/alice/. Alice's agent cannot widen these restrictions.
Alice's agent accumulates its own diary, facts, and session history entirely separately from every other agent. Her agent knows what she told it across many sessions. The main group's agent knows nothing about those conversations unless explicitly told.
This is the architecture working as intended: the group folder is the unit of memory isolation. One folder per user, one agent per folder. Backup Alice's agent: cp -r groups/main/alice groups/main/alice-backup. Move Alice to a different instance: copy the folder and re-register the JID.
The route table maps JID to group_folder. Group_folder encodes tier via depth. Tier constrains grants via NarrowRules. Grants filter the MCP manifest. The manifest determines what the agent can do.
This is one chain from four independent mechanisms — routing, hierarchy, grants, and manifests — each doing one job. Per-user agents emerge from these mechanisms without any feature specific to "users". A user is just a JID mapped to a folder. The rest follows from the design.