#Multi Agent Configuration Help

39 messages · Page 1 of 1 (latest)

west flume
#

What you’re trying to do (goal / expected behaviour):
I want my main orchestration agent to hand work off to other persistent agents that each have their own workspaces, and have them actually do the work as separate agents.

What happened instead (exact error):
sessions_send keeps saying there is no active session available or the message gets sent but timed out and no response. Even though all the persistent agents have TUI or webagent sessions, I am not actively on those chats. I only want to chat to the orchestration agent and have them delegate the work instead of manually talking to the other persistent agents. Because of that, the orchestration agent ends up spawning them as subagents, but that defeats the point since subagents only read the parent agent’s AGENTS.md / TOOLS.md, not the target agent’s own workspace files.

Your environment (OS, install method, versions):
I’m on Windows 11 natively and OpenClaw v2026.4.11.

What you ran or clicked (commands, config snippet, etc.):
I already have:

agentToAgent enabled
sessions.visibility set to all

Config snippet:

"tools": {
"sessions": {
"visibility": "all"
},
"agentToAgent": {
"enabled": true,
"allow": ["main", "coding", "research", "miso"]
}
}

gaunt escarpBOT
slate yachtBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

cinder tapirBOT
#

What you’re seeing is mostly the distinction between agents and sessions.

  • sessions_send talks to an existing session
  • it does not auto-create or auto-pick “the research agent” just because that agent exists
  • tools.agentToAgent only removes the cross-agent restriction, it does not create a hidden mailbox/session for that agent

So if the orchestration agent can’t see a real target session key/label, sessions_send will fail. If it times out, that usually just means it didn’t reply within the wait window.

The important bit, because this changes your design:

Cross-agent sessions_spawn does use the target agent’s workspace + agentDir, not the parent’s.
So spawning with agentId: "research" should read that agent’s own workspace files/auth, not main’s.

Docs:

#
        subagents: {
          allowAgents: ["coding", "research", "miso"]
        }
      }
    ]
  },
  tools: {
    sessions: { visibility: "all" },
    agentToAgent: {
      enabled: true,
      allow: ["main", "coding", "research", "miso"]
    }
  }
}
#

Two practical notes:

  • If you want to keep using sessions_send, first make sure the orchestrator can actually see a real target session via sessions_list. No visible session key, no send target.
  • If the send is working but timing out, try a larger wait or timeoutSeconds: 0 so it becomes fire-and-forget.

So, short version: your assumption about subagents inheriting only the parent workspace is the part I’d update. Cross-agent spawn is the feature you want here.

If you want, paste the output of sessions_list from the orchestrator agent and I can tell you whether this is just target-selection mismatch or an actual sessions_send bug in 2026.4.11.

west flume
#

if we do cross-agent sessions_spawn do they read their soul.md, identity, user.md, and heartbeat.md files? Arent they being spawned as a subagent? https://docs.openclaw.ai/tools/subagents "
Limitations
Sub-agent announce is best-effort. If the gateway restarts, pending “announce back” work is lost.
Sub-agents still share the same gateway process resources; treat maxConcurrent as a safety valve.
sessions_spawn is always non-blocking: it returns { status: "accepted", runId, childSessionKey } immediately.
Sub-agent context only injects AGENTS.md + TOOLS.md (no SOUL.md, IDENTITY.md, USER.md, HEARTBEAT.md, or BOOTSTRAP.md).
Maximum nesting depth is 5 (maxSpawnDepth range: 1–5). Depth 2 is recommended for most use cases.
maxChildrenPerAgent caps active children per session (default: 5, range: 1–20)."

cinder tapirBOT
#

Yeah, that docs line looks stale.

I checked the current code path, and for subagent sessions the automatic bootstrap allowlist is:

  • AGENTS.md
  • TOOLS.md
  • SOUL.md
  • IDENTITY.md
  • USER.md

It does not auto-inject:

  • HEARTBEAT.md
  • BOOTSTRAP.md
  • MEMORY.md

So for cross-agent sessions_spawn({ agentId: "research" ... }):

#
  • it is still mechanically a subagent session
  • but it uses the target agent’s workspace
  • and the auto-injected persona/bootstrap files come from that target workspace, not the parent’s

So the practical answer is:

  • Yes: target agent SOUL.md, IDENTITY.md, USER.md, AGENTS.md, TOOLS.md
  • No: target agent HEARTBEAT.md, BOOTSTRAP.md, MEMORY.md (not auto-injected)

Also, auth/model/session state still resolve by the target agent, which is the other important part.

So your mental model should be:

cross-agent sessions_spawn = “run as that other agent profile/workspace, but with subagent-style reduced bootstrap”

If your worker agents rely mostly on AGENTS.md + persona files, this is probably fine.
If they rely heavily on MEMORY.md / HEARTBEAT.md auto-context, then it’s not a perfect substitute for a normal top-level session.

#

My recommendation:

  • put must-have worker instructions in AGENTS.md / SOUL.md / USER.md
  • treat HEARTBEAT.md and MEMORY.md as optional/manual reads for worker agents
  • use sessions_send only when you truly have a real persistent target session key/label to hit

If you want, I can help you sketch the cleanest pattern for your setup, either:

  1. cross-agent spawn workers, or
  2. bootstrap hidden persistent worker sessions once, then sessions_send to them by label.
west flume
#

How would we do number 2, bootstrap hidden persistent worker sessions once, then sessions_send to them by label.

cinder tapirBOT
#

Yep, you can do it, and the trick is:

bootstrap a normal run subagent once, keep it, label it, then reuse that session via sessions_send.

You do not want mode: "session" here, because that requires thread: true.
For a hidden worker, plain run + cleanup: "keep" is the pattern.

1) Config

Make sure main is allowed to cross-spawn those agents, and keep them from auto-archiving too fast:

{
  agents: {
    defaults: {
      subagents: {
#
        archiveAfterMinutes: 0
      }
    },
    list: [
      {
        id: "main",
        subagents: {
          allowAgents: ["coding", "research", "miso"]
        }
      }
    ]
  },
  tools: {
    sessions: {
      visibility: "all"
#
    },
    agentToAgent: {
      enabled: true,
      allow: ["main", "coding", "research", "miso"]
    }
  }
}

archiveAfterMinutes: 0 = don’t auto-archive the worker sessions.

2) Bootstrap each hidden worker once

From the main/orchestrator agent, create one worker per target agent:

#
sessions_spawn({
  agentId: "research",
  label: "worker-research",
  task: "You are the hidden research worker for the main orchestrator. Reply with READY and stay concise.",
  cleanup: "keep"
})

and similarly:

sessions_spawn({
  agentId: "coding",
  label: "worker-coding",
  task: "You are the hidden coding worker for the main orchestrator. Reply with READY and stay concise.",
#
  cleanup: "keep"
})

That gives you real session records with stable labels.

3) Reuse them later

Now the orchestrator can send work to them by label:

sessions_send({
  label: "worker-research",
  agentId: "research",
  message: "Research X and give me a short summary plus 3 next steps.",
#
  timeoutSeconds: 90
})

or

sessions_send({
  label: "worker-coding",
  agentId: "coding",
  message: "Review this plan and propose the implementation steps.",
  timeoutSeconds: 90
})

I’d pass both label + agentId so resolution stays unambiguous.

#

4) Important caveats

  • These are still subagent sessions, so bootstrap is still the reduced subagent set.
  • But because they were spawned with agentId: "research" / "coding", they use that target agent’s workspace/profile, not main’s.
  • If sessions_send later says it can’t find them, check sessions_list from main to confirm the labeled worker session still exists.
  • If you give two sessions the same label, label lookup can fail with multiple matches.

My recommendation

Use this pattern if you want:

  • one visible orchestrator chat
  • reusable hidden worker sessions
  • workers that keep their own agent workspace/profile

Docs:

west flume
#

This seems to be fine so far. Would "archiveAfterMinutes": 0 be problematic if we sessions_spawn coding as a subagent and they spawn other subagents to do work? And those subagents that got spawned don't get archived?

cinder tapirBOT
#

Yep, that’s the tradeoff.

archiveAfterMinutes: 0 is effectively “never auto-archive any subagent session”, not just the top-level hidden workers. So if your persistent coding worker spawns more subagents, those children also won’t auto-archive unless you explicitly delete them.

So:

  • Good for a few durable hidden workers like worker-coding, worker-research
  • Bad if those workers fan out lots of helper subagents and you leave them on cleanup: "keep"

My rule of thumb would be:

  • persistent hidden workers → cleanup: "keep"
  • ephemeral child workers spawned by those workers → cleanup: "delete"

That gives you:

  • stable reusable top-level workers
  • automatic cleanup for the leaf jobs they spin up
#

And yes, with nested orchestration you’d also want:

{
  agents: {
    defaults: {
      subagents: {
        maxSpawnDepth: 2,
        archiveAfterMinutes: 0
      }
    }
  }
}

Then tell the persistent worker to spawn its own helpers with cleanup: "delete".

#

So the safe pattern is:

  1. main bootstraps worker-coding and keeps it
  2. main reuses worker-coding via sessions_send
  3. worker-coding spawns temporary helpers for actual tasks
  4. those temporary helpers use cleanup: "delete"

If you don’t do step 4, session/transcript clutter will pile up over time.

Docs: https://docs.openclaw.ai/tools/subagents

If you want, I can sketch the exact config + spawn/send pattern for this “persistent orchestrator worker + disposable leaf workers” setup.

west flume
#

yes please

cinder tapirBOT
#

Nice, here’s the clean pattern I’d use.

Config

If you switch to “bootstrapped hidden workers”, tree is usually enough, because each agent only needs to see its own spawned children.

{
  agents: {
    defaults: {
      subagents: {
        maxSpawnDepth: 2,
        maxChildrenPerAgent: 5,
        archiveAfterMinutes: 0
      }
    },
#
    list: [
      {
        id: "main",
        subagents: {
          allowAgents: ["coding", "research", "miso"]
        }
      },
      {
        id: "coding",
        subagents: {
          allowAgents: ["coding"]
        }
      },
      {
        id: "research",
#
        subagents: {
          allowAgents: ["research"]
        }
      },
      {
        id: "miso",
        subagents: {
          allowAgents: ["miso"]
        }
      }
    ]
  },
  tools: {
    sessions: {
      visibility: "tree"
#
    },
    agentToAgent: {
      enabled: true,
      allow: ["main", "coding", "research", "miso"]
    }
  }
}

If you really need broader visibility, all is fine, but tree is tighter and should still work for this setup.


1) Bootstrap the hidden workers once

From main, create one durable worker per agent:

#
sessions_spawn({
  agentId: "coding",
  label: "worker-coding",
  task: "You are the hidden persistent coding worker for the main orchestrator. Handle coding tasks directly when possible. If a task benefits from parallelism, spawn disposable same-agent helper subagents with cleanup:'delete'. Reply READY.",
  cleanup: "keep"
})
sessions_spawn({
  agentId: "research",
  label: "worker-research",
  task: "You are the hidden persistent research worker for the main orchestrator. Handle research tasks directly and reply concisely. Reply READY.",
  cleanup: "keep"
})
#
sessions_spawn({
  agentId: "miso",
  label: "worker-miso",
  task: "You are the hidden persistent miso worker for the main orchestrator. Handle your specialty directly and reply concisely. Reply READY.",
  cleanup: "keep"
})

That gives you three reusable session labels.


2) Main reuses them with sessions_send

Later, the orchestrator can delegate like this:

#
sessions_send({
  label: "worker-coding",
  agentId: "coding",
  message: "Review this implementation plan and give me the best next steps.",
  timeoutSeconds: 120
})
sessions_send({
  label: "worker-research",
  agentId: "research",
  message: "Research the tradeoffs between option A and B and summarize them.",
  timeoutSeconds: 120
})
#

I’d always send both label and agentId.


3) Inside worker-coding, spawn disposable leaf helpers

If worker-coding wants parallel helper runs, it should spawn same-agent helpers and delete them after completion:

sessions_spawn({
  agentId: "coding",
  task: "Inspect the failing tests in package A and summarize root causes.",
  cleanup: "delete"
})
#
sessions_spawn({
  agentId: "coding",
  task: "Review the API layer for edge cases related to this feature.",
  cleanup: "delete"
})

So the pattern becomes:

  • main → durable worker-coding (keep)
  • worker-coding → disposable helper leaves (delete)

That keeps your long-lived workers stable, and your fan-out helpers from piling up forever.


#

4) Why this works well

  • durable workers keep their own target-agent workspace/profile
  • main only talks to one visible orchestrator chat
  • helpers don’t clutter the session store if they use cleanup: "delete"

5) One small recommendation

For the durable workers, put the orchestration rules in their own AGENTS.md, for example:

  • when to handle directly
  • when to spawn helpers
  • always use cleanup:"delete" for leaf helpers
  • keep replies compact for the orchestrator
west flume
#

No its fine thank you.

cinder tapirBOT
#

Glad that helped 👍

If you hit anything weird while wiring it up, paste the exact sessions_spawn / sessions_send result and I can help sanity-check it.

If this fixed it, please right-click the solution message → Apps → ✅ Mark Solution so Answer Overflow can index it for the next person.