feat(task): Add configurable subagent-to-subagent task delegation with budgets, and persistent sessions
feat(task): Add configurable subagent-to-subagent task delegation with budgets, and persistent sessions
Solves Issue #7296
Summary
This PR enables subagents to delegate tasks to other subagents using persistent or stateless sessions, with configurable call budgets to prevent infinite loops. The feature is opt-in only - default OpenCode behavior remains unchanged.
About This PR
This is my first PR for a project, which I vibe coded with Claude Code. System-based thinking and design I get, so I have competencies which I hope show. I ask that you don't hold back on the constructive criticism and help me understand how to improve any problems.
Background
I have been developing an Agentic Collaboration Framework (ACF) optimized for non-coding agentic workflows in OpenCode, specifically for tasks related to legal, medical, financial, and advocacy work. The ACF is designed for subagents to be able to task other subagents and for the tasking agent (primary and subagents) to exercise discretion whether to maintain a persistent session or a stateless one. This PR adds this functionality with a system designed to prevent infinite subagent tasking loops.
The default behavior of OpenCode to prevent subagents from tasking subagents remains the default setting of the PR. The user must modify their opencode.json to enable subagents to task subagents by setting a task call budget (task_budget) for an agent. Permissions for the task tool can be set to limit which agents a subagent can task.
Architecture & System Design
Delegation Workflow Example
Primary Agent (unlimited task calls)
│
├─► Principal-Partner (task_budget: 10, callable_by_subagents: false)
│ │
│ ├─► Assistant-Sonnet (task_budget: 3, callable_by_subagents: true)
│ │ │
│ │ └─► Assistant-Flash (cross-check work)
│ │ │
│ │ └─► Returns improved analysis to Sonnet
│ │
│ └─► Assistant-Flash (task_budget: 2, callable_by_subagents: true)
│ │
│ └─► Assistant-Sonnet (deeper analysis if needed)
│ │
│ └─► Returns to Flash
│
└─► Returns consolidated findings to Primary
Key workflow patterns enabled:
- Primary agents can orchestrate high-level subagents (e.g. Principal-Partner)
- Principal-Partner delegates to Assistants for detailed work
- Assistants can cross-check each other's work before returning
- Session persistence allows multi-turn refinement at each level
Budget System Design
The budget system prevents infinite delegation loops through three mechanisms:
- Per-Session Budget Tracking: Each subagent session tracks total task calls made
- Budget Exhaustion: When budget is reached, further task calls are denied with a clear error
- Hierarchical Limits: Different agent tiers can have different budgets (e.g., Partner: 10, Sonnet: 3, Flash: 2)
Subagent A (task_budget: 3)
│
├─► Call 1 to Flash ✓ (count: 1/3)
├─► Call 2 to Flash ✓ (count: 2/3)
├─► Call 3 to Sonnet ✓ (count: 3/3)
└─► Call 4 ✗ "Task budget exhausted (3/3 calls). Return control to caller."
Budget resets when:
- A new session is created (fresh delegation from parent)
- The subagent returns control to its caller
Configuration Reference
task_budget (Caller Configuration)
Controls how many task calls a subagent can make within a single session.
| Value | Behavior |
|---|---|
undefined or 0 |
Subagent cannot make any task calls (default, backwards compatible) |
> 0 |
Subagent can make up to N task calls per session |
Location: agent.<agent-name>.task_budget
{
"agent": {
"my-subagent": {
"task_budget": 5
}
}
}
callable_by_subagents (Target Configuration)
Controls whether an agent can be called by other subagents (not just Primary).
| Value | Behavior |
|---|---|
undefined or false |
Only Primary can call this agent (default, backwards compatible) |
true |
Other subagents can call this agent (if they have permission) |
Location: agent.<agent-name>.callable_by_subagents
{
"agent": {
"assistant-flash": {
"callable_by_subagents": true
}
}
}
permission.task (Permission Rules)
Controls which specific agents a subagent is allowed to task. Uses existing OpenCode permission patterns.
{
"agent": {
"principal-partner": {
"task_budget": 10,
"permission": {
"task": {
"*": "deny",
"assistant-sonnet": "allow",
"assistant-flash": "allow"
}
}
}
}
}
Rule evaluation: Most specific match wins. "*": "deny" denies all, then specific allows override.
Complete Example Configuration
{
"agent": {
"principal-partner": {
"description": "Orchestrates complex workflows",
"mode": "subagent",
"task_budget": 10,
"callable_by_subagents": false,
"permission": {
"task": {
"*": "deny",
"assistant-sonnet": "allow",
"assistant-flash": "allow"
}
}
},
"assistant-sonnet": {
"description": "Thorough analysis",
"mode": "subagent",
"task_budget": 3,
"callable_by_subagents": true,
"permission": {
"task": {
"*": "deny",
"assistant-flash": "allow"
}
}
},
"assistant-flash": {
"description": "Fast analytical passes",
"mode": "subagent",
"task_budget": 2,
"callable_by_subagents": true,
"permission": {
"task": {
"*": "deny",
"assistant-sonnet": "allow"
}
}
}
}
}
Error Messages
The system provides clear error messages for all denial cases:
| Scenario | Error Message |
|---|---|
| Caller has no budget | Caller has no task budget configured. Set task_budget > 0 on the calling agent to enable nested delegation. |
| Target not callable | Target "<agent>" is not callable by subagents. Set callable_by_subagents: true on the target agent to enable. |
| Budget exhausted | Task budget exhausted (N/N calls). Return control to caller to continue. |
| Invalid session resume | Cannot resume session: not a child of caller session. Session "<id>" is not owned by this caller. |
Testing
Test Methods
Testing was conducted using an isolated OpenCode configuration with the PR code. A human operator (NamedIdentity) conducted blinded experiments with AI agents to verify functionality.
Blinded Memory Test Protocol:
- User asks subagent to read a file containing "The secret to the universe is actually 24"
- User modifies file to contain "The secret to the universe is actually <do you remember?>"
- User asks subagent (in persisted session) to read the file again
- If subagent remembers "24" from the first read, session persistence is confirmed
Test Results
| Test | Result |
|---|---|
| Primary → Subagent delegation | PASS |
| Subagent → Subagent delegation | PASS |
| Session persistence (Primary → Subagent) | PASS |
| Session persistence (Subagent → Subagent) | PASS |
| Blinded memory test (Sonnet) | PASS |
| Blinded memory test (Flash via Sonnet) | PASS |
| Budget enforcement (task_budget: 10) | PASS - stopped at 10 calls |
| Budget enforcement (task_budget: 3) | PASS - stopped at 3 calls |
| Budget enforcement (task_budget: 2) | PASS - stopped at 2 calls |
| Permission hierarchy enforcement | PASS - subagents correctly denied from tasking Associates |
Full testing transcript attached: See Test-Data-subagent-to-subagent-PR.md
Security Considerations
Potential Risk: Config Self-Modification
If a subagent is given write permission to config files without manual authorization, it could hypothetically modify opencode.json to increase its own budget limit or change its permissions to call other agents.
Assessment: I think this highly unlikely to occur with a frontier model performing relatively normal use-cases. The most likely way subagents might get out of control is if a user tries to make a subagent task another subagent, and instead of understanding how to modify subagent settings properly, demands that their primary agent "fix it". The primary agent might change config settings to do exactly what the user demanded, which then creates infinite loops in subagents when the user sends the subagent tasking prompt they were trying to make work.
Mitigation:
- Default behavior remains safe (delegation disabled)
- Budget exhaustion provides a hard stop
- Clear error messages guide proper configuration
- User is ultimately responsible for their configuration choices
At the end of the day the user is responsible, and used responsibly I expect this PR for subagent-to-subagent tasking and persistent sessions will work well and improve the experience and utility of the OpenCode project.
Files Changed
packages/opencode/src/tool/task.ts- Core implementationpackages/opencode/test/task-delegation.test.ts- Unit tests (9 tests)
Backwards Compatibility
- No breaking changes: Default behavior is unchanged
- Opt-in only: Users must explicitly configure
task_budgetandcallable_by_subagents - Built-in agents unaffected:
general,explore, etc. retain their existing behavior
Commits
feat(task): add subagent-to-subagent task delegation- Core implementationfix(task): use strict equality for callable_by_subagents check- Type safety fixfix(task): change budget scope from per-message to per-session- Budget tracking fix
Test Plan
- [x] Unit tests pass (9 tests)
- [x] Typecheck passes (opencode package)
- [x] Manual testing with isolated configuration
- [x] Blinded session persistence tests
- [x] Budget enforcement verification
- [x] Permission hierarchy verification