Skip to content

Conversation

@NamedIdentity
Copy link

@NamedIdentity NamedIdentity commented Jan 11, 2026

feat(task): Add configurable subagent-to-subagent task delegation with budgets, and persistent sessions

Fixes 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:

  1. Per-Session Budget Tracking: Each subagent session tracks total task calls made
  2. Budget Exhaustion: When budget is reached, further task calls are denied with a clear error
  3. 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:

  1. User asks subagent to read a file containing "The secret to the universe is actually 24"
  2. User modifies file to contain "The secret to the universe is actually <do you remember?>"
  3. User asks subagent (in persisted session) to read the file again
  4. 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 implementation
  • packages/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_budget and callable_by_subagents
  • Built-in agents unaffected: general, explore, etc. retain their existing behavior

Commits

  1. feat(task): add subagent-to-subagent task delegation - Core implementation
  2. fix(task): use strict equality for callable_by_subagents check - Type safety fix
  3. fix(task): change budget scope from per-message to per-session - Budget tracking fix

Test Plan

  • Unit tests pass (9 tests)
  • Typecheck passes (opencode package)
  • Manual testing with isolated configuration
  • Blinded session persistence tests
  • Budget enforcement verification
  • Permission hierarchy verification

NamedIdentity and others added 3 commits January 10, 2026 23:09
…e limits

Enable nested task delegation between subagents with two-dimensional
configuration:

- task_budget (CALLER): max task calls per request (messageID)
- callable_by_subagents (TARGET): whether agent can be called by subagents

Key changes:
- Add budget tracking per (sessionID, messageID) for per-request limits
- Check caller's task_budget before allowing delegation
- Check target's callable_by_subagents before allowing calls
- Validate session ownership before resuming with session_id
- Primary agents bypass all nested delegation controls
- Conditionally enable/disable task tool based on target's task_budget

Backwards compatible: missing config = delegation disabled (current behavior)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use === true instead of truthy coercion to prevent accidental
enablement from misconfigured values like "yes" or 1.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The task_budget was incorrectly keyed by (sessionID, messageID), causing
the budget counter to reset every turn since each assistant response
generates a new messageID. Changed to per-session tracking so all task
calls within a delegated session count toward the same budget.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Contributor

Hey! Your PR title # feat(task): Add configurable subagent-to-subagent task delegation with budgets, and persistent sessions doesn't follow conventional commit format.

Please update it to start with one of:

  • feat: or feat(scope): new feature
  • fix: or fix(scope): bug fix
  • docs: or docs(scope): documentation changes
  • chore: or chore(scope): maintenance tasks
  • refactor: or refactor(scope): code refactoring
  • test: or test(scope): adding or updating tests

Where scope is the package name (e.g., app, desktop, opencode).

See CONTRIBUTING.md for details.

@github-actions
Copy link
Contributor

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

@NamedIdentity NamedIdentity changed the title # 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 Jan 11, 2026
@github-actions
Copy link
Contributor

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant