Grant rules

The MCP manifest is the policy. Agents cannot call tools they cannot see.

Grant rules control which MCP tools an agent can use. They are plain text files in the group folder, evaluated at container spawn time. The result is a filtered MCP manifest — the agent receives only the tools it is permitted to call. There is no second enforcement layer; the manifest is the only check.

Rule syntax

*                              # allow everything
send_reply                     # allow a specific tool
!send_document                 # deny a specific tool
send_message(jid=telegram:*)   # allow with argument constraint
!delegate_to_child             # prevent delegation to children

Rules are evaluated top to bottom. An explicit deny (!) beats an earlier allow. The wildcard * is only meaningful as the first rule — it grants everything, subject to subsequent denies.

Default grants by tier

TierDefault rulesTypical use
0 (groups/main/)*Operator-controlled groups, full access
1 (groups/main/team/)send_message
send_reply
spawn_group
Trusted subgroups with delegation
2 (groups/main/team/pub/)send_replyPublic-facing groups, reply only

Tier is determined by folder depth from groups/. No permission table. Depth is the default.

Setting grants on a group

Create a .grants file in the group folder:

# groups/main/research/.grants
send_reply
read_diary
!delegate_to_child
!spawn_group

Or have the agent set its own grants via the set_grants MCP tool — subject to the narrowing invariant:

set_grants(["send_reply", "read_diary", "!send_document"])

Narrowing invariant: grants can only restrict

When a group spawns a child group, the child inherits the parent's grants and can only narrow them further. grants.NarrowRules(parent, child) merges the two rule sets:

// parent grants: ["send_message", "send_reply", "spawn_group"]
// child requests: ["send_message", "send_reply", "spawn_group", "read_db"]
// result: ["send_message", "send_reply", "spawn_group"]
// — read_db dropped because parent does not have it

The merged rules go into start.json before the container starts. The buildMCPServer function filters the tool list against these rules before sending the manifest to the agent. An agent that cannot see a tool cannot call it.

Argument constraints

Rules can constrain argument values, not just tool names:

send_message(jid=telegram:*)    # can only send to telegram: JIDs
send_message(jid=telegram:-100*) # can only send to telegram groups (negative IDs)

This is enforced at the MCP handler level: the gateway checks the rule constraint before executing the tool call. An agent cannot bypass it by calling the tool with a different JID.

Example: public-facing group

A group that handles public Telegram messages should be able to reply but not send unsolicited messages, not spawn subgroups, and not delegate:

# groups/main/public/.grants
send_reply
!send_message
!spawn_group
!delegate_to_child
!schedule_task

The agent in this group receives a manifest with only send_reply (plus read-only tools like get_facts). It literally cannot call send_message — the tool is not in its manifest.

How it fits the system

grants.NarrowRules is a structural invariant. A tier-1 group cannot grant tier-0 permissions to its children, because NarrowRules removes any rule that the parent does not have. This is enforced in code, not by policy documentation. The hierarchy is enforced by the merger function.

Because the manifest is the policy, there is no second check — no middleware, no authorization layer in the tool handler (except for argument constraints). The filter runs once at container spawn. The agent's capabilities are fixed for the lifetime of that container invocation. This is why the manifest is described as the policy: it is not just a description of what is allowed; it is the mechanism that makes other tools unreachable.