Per-user agents

Groups are directories. A per-user agent is a group folder with one registered JID.

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.

Create a child group for a user

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 the user's JID to their group

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.

The resulting folder structure

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.

Route table state

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.

Grant inheritance

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.

Private memory

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.

How it fits the system

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.