-
-
Notifications
You must be signed in to change notification settings - Fork 1
Subagents
M31 Autonomous (M31A) supports parallel subagents that run in isolated git worktrees. Subagents allow the LLM to spawn child agents for independent tasks, each with their own dispatcher and working directory.
graph TD
Parent[Parent Agent<br/>main TUI session] --> S1[Subagent 1<br/>isolated worktree<br/>own dispatcher]
Parent --> S2[Subagent 2<br/>isolated worktree<br/>own dispatcher]
Parent --> S3[Subagent 3<br/>isolated worktree<br/>own dispatcher]
S1 --> |"events channel"| Parent
S2 --> |"events channel"| Parent
S3 --> |"events channel"| Parent
style Parent fill:#e1f5fe
style S1 fill:#e8f5e9
style S2 fill:#e8f5e9
style S3 fill:#e8f5e9
Each subagent:
- Gets its own git worktree (created via
git worktree add) - Gets its own tool dispatcher (with independent permissions)
- Runs in a background goroutine
- Reports results via an event channel
Source: internal/tools/subagent/manager.go
type Dependencies struct {
WorkDir string
Registry *provider.Registry
ActiveModel *types.ModelInfo
Logger *slog.Logger
Worktrees WorktreeOps
NewDispatcher DispatcherFactory
}The Manager:
- Tracks active subagents by ID
- Provides
Spawn()to create new subagents - Provides
Cancel()andShutdown()for cleanup - Exposes an
Events()channel for TUI integration
| Limit | Value | Description |
|---|---|---|
MaxTotalSubagents |
50 | Session-wide spawn limit |
MaxSpawnRate |
10/min | Per-minute spawn rate tracking |
shutdownTimeout |
5s | Graceful shutdown timeout |
Source: internal/tools/subagent/loop.go
Each subagent runs its own agent loop:
- Builds system prompt and tool definitions
- Sends messages to the LLM
- Parses tool calls from the response
- Executes tools via its own dispatcher
- Feeds results back to the LLM
- Repeats until the task is complete or context is exhausted
Source: internal/tools/subagent/loop_parse.go
Parses LLM responses for tool calls, handling both native tool_call chunks and text-embedded tool invocations.
Source: internal/tools/subagent/worktree.go
Each subagent gets an isolated working directory:
git worktree add .m31a/worktrees/agent-<id> -b m31a/agent-<id>Benefits:
- File edits don't conflict between parallel agents
- Each agent can commit independently
- Cleanup on completion:
git worktree remove+ branch deletion
On startup, Sweep() removes leftover worktrees and branches from previous crashes:
- Removes orphaned
.m31a/worktrees/directories - Cleans up
m31a/agent-*branches
Source: internal/tools/subagent/events.go
Subagent events are streamed to the TUI:
| Event | Description |
|---|---|
SubagentStartedMsg |
Agent spawned with ID and description |
SubagentProgressMsg |
Intermediate output from the agent |
SubagentCompleteMsg |
Agent finished with result or error |
SubagentToolCallMsg |
Tool invocation within a subagent |
The TUI listens via subagentListenerCmd() and renders events as toast notifications or sidebar entries.
Source: internal/tools/agent.go
The Agent tool is registered on the parent dispatcher, allowing the LLM to spawn subagents:
{
"name": "Agent",
"description": "Spawn a parallel subagent...",
"params": {
"description": "Short task description",
"prompt": "Detailed instructions for the subagent"
}
}Child agents are created with isChild=true, which prevents them from spawning grandchildren in background. This avoids runaway recursion.
Source: internal/tools/subagent/profile.go
Subagents can use named profiles that define their system prompt, tool access, model, and resource budgets.
type AgentProfile struct {
Name string
Description string
Mode string // "primary", "subagent", "all"
SystemPrompt string
Model string // override model ID; "" = inherit parent
Hidden bool
AllowedTools []string // allowlist (empty = all tools)
DeniedTools []string // denylist (applied after allowlist)
MaxTools int // 0 = use manager defaults
MaxTokens int
MaxTurns int
}| Profile | Description | Tools |
|---|---|---|
build |
Primary agent with full tool access | All tools |
plan |
Read-only except plan documents | Glob, Grep, FileRead, FileWrite (plan docs only) |
general |
General-purpose subagent | All tools (denies TodoWrite, Agent) |
explore |
File search specialist | Glob, Grep, FileRead, FileList, CodeMap, CodeComplexity, WebFetch, WebSearch |
security |
Security audit specialist | Glob, Grep, FileRead, Bash, WebFetch, WebSearch (denies FileWrite, Edit, FileDelete, FileMove) |
review |
Code review agent | Glob, Grep, FileRead, Bash, WebFetch, WebSearch (denies FileWrite, Edit, FileDelete, FileMove) |
[agents.profiles]
[agents.profiles.explore]
description = "Custom explore agent"
model = "gpt-4"
allowed_tools = ["Glob", "Grep", "FileRead"]
max_turns = 20
disabled = falseProfile resolution follows priority order:
- Explicit
profileparameter in the Agent tool call - Config overrides via
[agents.profiles] - Built-in profile defaults
- Global default (general-purpose)
Source: internal/engine/workflow/agent_switch.go
The workflow engine supports dynamic agent switching during execution:
- Planner → Builder — After plan completion, the engine suggests switching from a planner agent to a builder agent
-
Plan file creation — Plan content is written to
.m31a/plans/<session-id>.md - Context injection — A synthetic user message instructs the builder agent to execute the plan
- Profile resolution — The target agent's profile controls system prompt, tools, and model
Subagents support native streaming tool calls from the LLM:
- Tool call chunks are accumulated by index during SSE streaming
- Each chunk's
tool_call_id,function.name, andfunction.argumentsare merged - When a
finish_reason: tool_callsis received, accumulated calls are finalized - Tool calls are executed via the subagent's own dispatcher
- Results are fed back to the LLM for the next iteration
This enables real-time visibility into subagent progress via the events channel.
| Command | Description |
|---|---|
/agent [desc]: [prompt] |
Spawn a new subagent |
/agent |
List active subagents |
/agent-cancel <id|all> |
Cancel a running subagent |
Source: internal/ui/tui/subagents_model.go, internal/ui/tui/subagent_bridge.go
The TUI maintains a SubagentsModel that:
- Lists active subagents with status
- Shows progress and results
- Provides cancel functionality
- Integrates with the sidebar for status display