How far the session and subagent tools reach: which agents may call each other, which sessions they can target, and the defaults applied to spawned sub-agents.

tools.agentToAgent

{
  tools: {
    agentToAgent: {
      allow: ["home", "work"],
    },
  },
}

Cross-agent access is on by default. enabled (default true) gates cross-agent session tool calls: sessions_send to another agent, and cross-agent sessions_list, sessions_history, sessions_search, and status reads under the default tools.sessions.visibility: "all". Set enabled: false to turn cross-agent access off. Same-agent access never consults this policy. Requester-owned native subagent and ACP child sessions are the one exception: under tree or all visibility they stay reachable across agent boundaries before this policy is consulted, including with enabled: false.

allow lists the agent ids or * patterns that may take part in a cross-agent call. Both the requesting agent and the target agent must match an entry. Exact ids are case-sensitive; wildcard patterns are case-insensitive.

An omitted or empty allow counts as unset: with agent-to-agent access enabled by default, every agent can reach every other agent. List every participating agent, requester and target alike, to restrict cross-agent access, as in the example above. A list containing only blank entries denies all cross-agent calls. Deleting an agent (apus agents delete) prunes its id from allow; if that empties the list, the policy falls back to allow-all, so re-check allow after removing agents.

Per-agent send-only access

Use agents.entries.<agentId>.tools.agentToAgent.send to let one agent send requests to selected agents without widening session visibility. It does not remove existing read access: keep tools.sessions.visibility below all when you want send-only access. For example, let specialist ask ops for help without granting direct cross-agent history access:

{
  tools: {
    agentToAgent: { enabled: true, allow: ["specialist", "ops"] },
    sessions: { visibility: "agent" },
  },
  agents: {
    ownership: "explicit",
    entries: {
      specialist: {
        tools: { agentToAgent: { send: ["ops"] } },
      },
      ops: {
        tools: { agentToAgent: { send: [] } },
      },
    },
  },
}

specialist can use sessions_send to reach ops and receive the reply to that sent turn, even though ops has send: []. ops cannot initiate ordinary cross-agent sends. Neither agent gains direct access to the other agent's history. The sessions_send tool must still be enabled by the caller's effective tool policy. The specialist can address the configured agent directly; it does not need to list the ops agent's sessions first. Call sessions_send with:

{ "agentId": "ops", "message": "Please check the service status." }

send is an optional string array:

Send-only access is permission to ask the target agent to act. It does not prevent a privileged target from disclosing data in its reply or executing an untrusted request. Keep target tools and instructions appropriate for incoming requests; use separate Gateways for strong isolation. See Security trust model.

tools.sessions

Controls which sessions can be targeted by the session tools (sessions_list, sessions_history, sessions_search, sessions_send, session_status).

Default: all (every session on the Gateway, including other agents' and other users' transcripts). Cross-agent access is governed by tools.agentToAgent and is on by default. Use agent, tree, or self to narrow visibility.

{
  tools: {
    sessions: {
      // "self" | "tree" | "agent" | "all"
      visibility: "all",
    },
  },
}
Visibility scopes
- `self`: only the current session key.
- `tree`: current session + sessions spawned by the current session (subagents). When the caller is the canonical main session, it includes every same-agent session for list, history, search, send, and status.
- `agent`: any session belonging to the current agent id (can include other users if you run per-sender sessions under the same agent id).
- `all`: any session. Cross-agent targeting is governed by `tools.agentToAgent`, which is on by default.
- `self` has no main-session visibility exception. Incognito denial remains absolute. Narrowing visibility to `agent`, `tree`, or `self` blocks ordinary cross-agent access unless a per-agent `tools.agentToAgent.send` rule permits a send-only exception. `tree` also permits owned native/ACP children across agent boundaries. `agent` does not include that child exception, so keep explicit `tree` if your workflow relies on it.
- Sandbox clamp: when the current session is sandboxed and `agents.defaults.sandbox.sessionToolsVisibility="spawned"` (the default), access stays limited to spawned sessions even if the caller is main or `tools.sessions.visibility="all"`.
- When not `all`, `sessions_list` includes a compact `visibility` field
  describing the effective mode and a warning that some sessions may be
  omitted outside the current scope.

Ambient group watches still queue activity notices and tell the main session where something happened. They do not grant access. The default all scope already covers sessions across agents, including conversations with other users. A per-peer session.dmScope separates DM context but does not restrict session tools. For narrower access, explicitly choose agent, tree, or self, or restrict agent pairs with tools.agentToAgent.allow. Set tools.agentToAgent.enabled: false to block ordinary cross-agent access; requester-owned native subagent and ACP child sessions stay reachable under tree or all. tree retains the canonical main-session exception; self restricts even main to its current session.

tools.sessions_spawn

Controls inline attachment support for sessions_spawn.

{
  tools: {
    sessions_spawn: {
      attachments: {
        enabled: false, // opt-in: set true to allow inline file attachments
        maxTotalBytes: 5242880, // 5 MB total across all files
        maxFiles: 50,
        maxFileBytes: 1048576, // 1 MB per file
        retainOnSessionKeep: false, // keep attachments when cleanup="keep"
      },
    },
  },
}
Attachment notes
- Attachments require `enabled: true`.
- Subagent attachments are staged in Gateway-owned state with a `.manifest.json`; they are never written through the child workspace.
- Sandboxed children receive only their session-owned attachments read-only at `/apus/attachments/<uuid>/`. Attachment-bearing agent-scoped sessions use a dedicated runtime so sibling guests cannot inherit the projection. Shared-scope sandboxes and backends without read-only resource projection reject attachment-bearing spawns before staging.
- Unsandboxed children receive the absolute Gateway-owned path and can read it through workspace-scoped file/media tools.
- ACP attachments are image-only and forwarded inline to the ACP runtime after the same file count, per-file byte, and total byte limits pass.
- Attachment content is automatically redacted from transcript persistence.
- Base64 inputs are validated with strict alphabet/padding checks and a pre-decode size guard.
- Subagent attachment file permissions are `0700` for directories and `0600` for files.
- Subagent cleanup follows the `cleanup` policy: `delete` always removes attachments; `keep` retains them only when `retainOnSessionKeep: true`.
- Upgrading from a pre-`readOnlyResourceMounts` release: previously staged attachments remain in their child workspaces at `.apus/attachments/<uuid>/`. Their registry records retire without deleting or traversing those files, so remove leftovers with your normal workspace cleanup. New spawns stage in Gateway-owned state; the child prompt path is the only usable filesystem location. The attachment receipt's `relDir` is a retained identifier, not a usable location, and must not be resolved.

agents.defaults.subagents

{
  agents: {
    defaults: {
      subagents: {
        allowAgents: ["research"],
        model: "minimax/MiniMax-M2.7",
        maxConcurrent: 8,
        runTimeoutSeconds: 900,
        announceTimeoutMs: 120000,
        archiveAfterMinutes: 60,
      },
    },
  },
}

tools.swarm

Swarm is enabled by default. Collector children (collect: true) run in a dedicated subagent:swarm:<schedulerGroupKey> lane, with the group's resolved maxConcurrent cap. They leave the parent's ordinary sub-agent lane available. Ordinary children spawned by a collector use that collector's own session lane.

{
  tools: {
    swarm: {
      maxConcurrent: 32,
      maxChildrenPerGroup: 50,
      maxTotalPerGroup: 200,
    },
  },
}

maxConcurrent defaults to 32 and accepts integers from 1 to 1000. Each running child costs one model stream and one Code Mode worker isolate. The separate maxChildrenPerGroup and maxTotalPerGroup admission limits remain 50 live children and 200 lifetime spawns per group by default. See Swarm configuration for all settings and per-agent overrides.