Skip to content

Conversation

@NamedIdentity
Copy link

@NamedIdentity NamedIdentity commented Jan 11, 2026

feat(task): Add subagent-to-subagent delegation with budgets, persistent sessions, and hierarchical session navigation #7756

Fixes #7296 #6183 #3291

Summary

This PR enables subagents to delegate tasks to other subagents using persistent or stateless sessions, with configurable call budgets and depth limits to prevent infinite loops. Delegation control uses OpenCode's existing permission system. The PR also adds a hierarchical multi-level TUI session navigation system which allows users to traverse nested session trees using clickable delegation boxes and keyboard shortcuts. A new Session Tree dialog (<leader>s) provides a visual tree view of all sessions in the hierarchy, enabling quick navigation to any session. The subagent session navigation system now allows viewing the sessions of background task subagent delegation (the asynchronous agents in oh-my-opencode v2.14.0).

The subagent tasking and persistent session feature is opt-in only - default OpenCode behavior remains unchanged.

The hierarchical session navigation feature changes OpenCode's default keybinds and UI for subagent session navigation and viewing.


About This PR

This is my first PR for a project, which I vibe coded with Claude Code and OpenCode. 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.


Problem Statement

Subagent Delegation Limitations

Subagents could not delegate tasks to other subagents, limiting complex multi-agent workflows. Users needed the ability to create hierarchical agent structures where high-level agents orchestrate lower-level agents.

TUI Navigation Limitations

Subagent sessions beyond the first level of nesting were inaccessible or difficult to navigate in the TUI. Specifically:

  1. No keyboard down-navigation: Users could only navigate up (to parent) or cycle siblings, but could not descend into child sessions using the keyboard
  2. Sibling cycling included all descendants: When cycling "siblings", ALL descendant sessions from the root were included, not just sessions at the same hierarchy level
  3. Text-only subagent returns not clickable: Task delegations that returned text without tool calls rendered as non-clickable InlineTool components instead of clickable BlockTool indicators
  4. Generic header without hierarchy context: Subagent sessions showed "Subagent session" without indicating where the user was in the hierarchy
  5. No overview of session hierarchy: Users had no way to see the full tree structure of sessions and jump directly to any session in the hierarchy

Related: PR #7756 comment by zackheil - user reported inability to view sub-sub agent transcripts.


Part 1: Subagent-to-Subagent Delegation

Key Features

  1. Subagent-to-subagent delegation: Subagents can now task other subagents using the Task tool
  2. Session persistence: Subagent sessions can be resumed across multiple task calls
  3. Budget system: Configurable task_budget per agent prevents infinite delegation loops
  4. Permission-based control: Uses OpenCode's existing permission.task system for granular delegation rules

What Was Fixed

  1. bypassAgentCheck bug: Subagent-to-subagent delegations now always check permissions (commit 1adf95c5e)
  2. Removed callable_by_subagents flag: Simplified to use permissions-only approach (commit 7e3e5a77b)

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, can task assistants)
    │       │
    │       ├─► Assistant-Sonnet (task_budget: 3, can task flash)
    │       │       │
    │       │       └─► Assistant-Flash (cross-check work)
    │       │               │
    │       │               └─► Returns improved analysis to Sonnet
    │       │
    │       └─► Assistant-Flash (task_budget: 2, can task sonnet)
    │               │
    │               └─► 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 to help with Partner's substantive, detailed work
  • Assistants can cross-check each other's work before returning
  • Session persistence allows multi-turn refinement at each level
  • Enables the creation and operation of subagents which can excute complex agentic workflows (e.g. Deep Research)

A Knowledge-Work Workflow Example: A Primary agent tasks a Deep Research subagent which orchestrates a complex agentic workflow. The Deep Research subagent returns a report to Primary. The Primary reviews the report, performs additional research based upon the 'big picture' context an orchestrating agent has, and then tasks two review agents to critic the report for specific issues. Primary reviews critics, and then issues new prompt to Deep Research subagent for futher research, analysyis, and revision of the existing report.

Budget System Design

The budget system prevents infinite delegation loops through four 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)
  4. Depth Limit: Global level_limit caps the maximum session tree depth (default: 5)
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
    }
  }
}

permission.task (Delegation Control)

Controls which specific agents a subagent is allowed to task. Uses OpenCode's existing permission system with pattern matching.

How it works:

  • By default, Primary agents can task any agent (existing OpenCode behavior)
  • For subagent-to-subagent delegation, the caller must have task_budget > 0 AND permission to task the target
  • Permission rules use pattern matching: "*" (all), agent names, wildcards

Example: Agent that can only task specific assistants:

{
  "agent": {
    "principal-partner": {
      "task_budget": 10,
      "permission": {
        "task": {
          "*": "deny",
          "assistant-sonnet": "allow",
          "assistant-flash": "allow"
        }
      }
    }
  }
}

Controlling who can task an agent:
Since permissions are caller-focused (not target-focused), to make an agent "callable by subagents," simply ensure calling agents have permission to task it. By default, agents have no permission to task others until explicitly granted.

Rule evaluation: Most specific pattern wins. "*": "deny" denies all, then specific allows override.

Complete Example Configuration

{
  "agent": {
    "principal-partner": {
      "description": "Orchestrates complex workflows",
      "mode": "subagent",
      "task_budget": 10,
      "permission": {
        "task": {
          "*": "deny",
          "assistant-sonnet": "allow",
          "assistant-flash": "allow"
        }
      }
    },
    "assistant-sonnet": {
      "description": "Thorough analysis",
      "mode": "subagent",
      "task_budget": 3,
      "permission": {
        "task": {
          "*": "deny",
          "assistant-flash": "allow"
        }
      }
    },
    "assistant-flash": {
      "description": "Fast analytical passes",
      "mode": "subagent",
      "task_budget": 2,
      "permission": {
        "task": {
          "*": "deny",
          "assistant-sonnet": "allow"
        }
      }
    }
  }
}

level_limit (Global Delegation Depth Limit)

Controls the maximum depth of subagent session trees to prevent infinite delegation loops.

Value Behavior
undefined Defaults to 5
0 Disabled (no limit) - NOT RECOMMENDED
> 0 Maximum session tree depth allowed

Location: experimental.level_limit

{
  "experimental": {
    "level_limit": 5
  }
}

How it works:

  • Depth is calculated by walking up the parentID chain
  • Root session = depth 0, first child = depth 1, etc.
  • When limit is reached, further task delegations are denied with clear error
  • Works alongside task_budget (horizontal limit per session) to provide complete loop prevention

Example with level_limit: 3:

Primary (depth 0)
└─► L1: Partner (depth 1)
    └─► L2: Sonnet (depth 2)
        └─► L3: Flash (depth 3)
            └─► L4: Sonnet ✗ DENIED - exceeds level_limit: 3

Complementary safeguards:

  • task_budget: Limits task calls PER SESSION (horizontal limit)
  • level_limit: Limits session tree DEPTH (vertical limit)

Both work together to prevent runaway delegation.

Error Messages

The system provides clear error messages for:

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.
Permission denied Permission denied: task permission for pattern "<agent>" (via existing OpenCode permission system)
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.
Level limit reached Level limit reached (depth N/M). Cannot create deeper subagent sessions. Return control to caller.

Configuration Requirements

IMPORTANT: For subagent-to-subagent delegation to work correctly with permissions, follow these configuration guidelines:

✅ Do This

Define permissions in JSON config only:

{
  "agent": {
    "assistant-sonnet": {
      "prompt": "{file:./agent/assistant-sonnet.md}",
      "task_budget": 3,
      "permission": {
        "task": { "*": "deny", "assistant-flash": "allow" },
        "edit": "ask",
        "bash": "ask"
      }
    }
  }
}

❌ Don't Do This

Don't include tools: { task: true } in agent markdown frontmatter:

---
# WRONG - This will override JSON permission rules
tools:
  task: true
---

Why?

OpenCode's config system loads markdown files AFTER JSON config. The legacy tools field gets converted to permission rules, and markdown values override JSON values in conflicts. To use pattern-based permission.task rules (like { "*": "deny", "agent-x": "allow" }), the tools field must not be present in frontmatter.

See: Artifacts_Agents/permission-bugs-and-fixes.md for detailed technical explanation.

Bugs Fixed During Development

Bug #1: bypassAgentCheck Propagation (FIXED)

Issue: Permission checks were being bypassed for subagent-to-subagent delegation when bypassAgentCheck flag was set.

Root Cause: The bypassAgentCheck flag (intended to allow user @agent invocations) was propagating to Task calls made BY subagents, causing them to bypass permission checks entirely.

Fix (commit 1adf95c5e): Modified task.ts to always enforce permissions for subagent-to-subagent delegation, even when bypassAgentCheck is set. Preserves OpenCode's intended behavior for user @agent invocations while ensuring subagent delegations always check permissions.

Impact: Subagent permission.task rules now work correctly.

Permission System Interactions (DOCUMENTED)

During testing, we identified how OpenCode's permission system interacts with subagent delegation. These are not bugs in OpenCode, but configuration requirements users must understand:

  1. Frontmatter precedence: Markdown frontmatter overrides JSON config due to load order
  2. Legacy tools conversion: tools: { task: true } becomes permission.task = "allow"
  3. Workaround: Define all permissions in JSON config, remove tools from frontmatter

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.


Part 2: Hierarchical Session Navigation

Addresses feedback: PR #7756 comment by zackheil - "I noticed I couldn't use ctrl+x, right to look at the sub-sub agent's transcript."

Solution

Implement a tree-based navigation model where:

  • Up/Down moves between hierarchy levels (parent/children)
  • Left/Right cycles only among sessions with the same direct parent
  • Root jumps directly to the root session from any depth

The subagent header is redesigned with three rows:

  1. Breadcrumb trail: Shows path from root to current session with clickable segments
  2. Stats row: Horizontal divider with token count, cost, and version
  3. Navigation hints: Context-aware hints that only show available navigation options/keybinds

New Keybinds

Keybind Action Default
session_child_down Navigate to first child session <leader>down
session_root Jump to root session <leader>escape

Navigation Logic Changes

  • siblings memo: New memo that filters sessions by x.parentID === session()?.parentID for accurate sibling cycling
  • directChildren memo: New memo that filters sessions by x.parentID === session()?.id for down navigation
  • moveChild(direction): Updated to use siblings() instead of children() for left/right cycling
  • moveToFirstChild(): New function to navigate to the first element of directChildren()
  • moveToRoot(): New function that traverses up the parentID chain to find and navigate to root

Header Component Rewrite

The subagent header (header.tsx) was significantly rewritten:

  • Session path computation: sessionPath memo walks up parentID chain to build ancestry array
  • Depth calculation: depth memo computes hierarchy level (0 = root)
  • Breadcrumb UI:
    • Clickable segments separated by " > "
    • Dynamic width-based truncation (shows root + "..." + last N segments that fit)
    • Current session displayed in bold
  • Agent name extraction: Parses session titles like "Description (@agent-name subagent)" to extract agent identifier
  • Adaptive navigation hints:
    • Labels use Parent (depth 1→root) or Child(Ln) notation (e.g., Child(L1), Child(L2))
    • Only shows available navigation directions
    • "Root" hint only appears at depth ≥ 2

Task Component Fix

Changed clickability condition from props.metadata.summary?.length to props.metadata.sessionId:

  • Ensures all completed task delegations are clickable, including text-only returns
  • Updated keybind hint from session_child_cycle to session_child_down

SDK Type Additions

Regenerated SDK types to include new keybinds:

  • session_child_down?: string
  • session_root?: string

oh-my-opencode Compatbility

While thie PR allows background task/asynchronous agents to be viewed in the subagent session navigation system via keybinds, it does not attempt to make the background tasks clickable in the tasking agents session. Unlike task delegation boxes, background tasks cannot be clicked into view. I believe oh-my-opencode needs to modify how it's codebase displays completed tasks, and that making the changes needed to both oh-my-opencode and OpenCode requires direct involvement of the maintainers in one or both projects.

All my testing was done with OpenCode 1.1.25 and dev-latest with oh-my-opencode v.2.14.0. As of 2026-01-22 have no data on OMO v.3+.

Known Limitation

Navigation uses the existing parentID (creation hierarchy) model. When an agent resumes a session it didn't create (e.g., Parent resumes Grandchild originally created by Child):

  • Clicking the delegation box: Works correctly
  • Keyboard down-navigation from Parent: Won't show the Grandchild as a "child" because Grandchild's parentID points to Child, not Parent

This is documented behavior. A future enhancement could introduce interaction tracking (callers[]) to handle this edge case.


Part 3: Session Tree Dialog

Related Issue: #6183

Solution

Add a Session Tree dialog that displays the full session hierarchy in a visual tree format. Users can open it with <leader>s or via the command palette ("Session tree") to see all sessions and navigate directly to any one.

Features

  • Tree visualization: Shows parent → child session hierarchy with box-drawing characters (├─, └─, │)
  • Status indicators:
    • Current session (primary color)
    • Session awaiting permission (warning color)
    • Spinner for busy/active sessions
  • Navigation: Click or use arrow keys + Enter to navigate to any session
  • Agent identification: Extracts agent name from session title (@agent-name pattern) or falls back to first word
  • Timestamps: Shows session update time (respects user's OS locale)

UI Example

Session Tree
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

● Help me refactor auth                                    14:22
├─ Principal-Partner "research requirements"               14:25
│  ├─ Assistant-Sonnet "review documentation"              14:26
│  │  └─ ◉ Assistant-Flash "audit report" (awaiting)       14:27
│  └─ Assistant-Flash "review best practices"              14:28
├─ State-Updater "update project-state"                    14:20
└─ explore "find auth standards"                           14:30

Keybind Changes

Keybind Command Before After
<leader>s session_child_list NEW - Opens session tree dialog
<leader>i status_view <leader>s Moved to <leader>i

Rationale for status_view Keybind Change

We intentionally moved status_view from <leader>s to <leader>i for the following reasons:

  1. Collision Resolution: Both status_view and the new session_child_list cannot share <leader>s. One had to move.

  2. Usage Frequency: Session tree navigation is expected to be a high-frequency action for users working with subagents (navigating between parent/child sessions repeatedly). Status panel is lower-frequency—users typically check status occasionally and often use /status command instead.

  3. Mnemonic Strength:

    • <leader>sSession tree (primary use case, high frequency)
    • <leader>iInfo/status (secondary use case, "info" is a common TUI convention)
  4. Reserving Prime Keys: <leader>s is a "prime" keybind (easy to type, strong mnemonic). Allocating it to the more frequently-used feature maximizes user efficiency.

  5. Alternative Considered: We evaluated ctrl+s for session tree but rejected it due to terminal flow control conflicts (XOFF) that could freeze the TUI in some terminal configurations.

Note for Maintainers

Consider polling users on whether <leader>t would be better for status_view:

  • If status panel is rarely used (users prefer /status), <leader>i is appropriate
  • If frequently used, <leader>t could provide faster muscle-memory access
  • Note: <leader>t is currently used by theme_list

Technical Notes

  • Uses existing DialogSelect component for consistent UI/UX
  • Tree built via DFS traversal from root session
  • Root session found by walking up parentID chain
  • Session type has no agent field; agent name extracted from title pattern or first word
  • Time display uses Locale.time() to respect user's OS locale setting

Files Changed

Subagent Delegation

File Changes
packages/opencode/src/tool/task.ts Core implementation - budget tracking, permission enforcement, session persistence
packages/opencode/test/task-delegation.test.ts Unit tests (6 tests)

TUI Session Navigation

File Changes
packages/opencode/src/config/config.ts Added session_child_down, session_root, and session_child_list keybind schema entries; changed status_view default from <leader>s to <leader>i
packages/opencode/src/cli/cmd/tui/routes/session/index.tsx Added siblings and directChildren memos; added moveToFirstChild() and moveToRoot() functions; registered session.child.down, session.root, and session.tree commands; fixed Task clickability condition; added DialogSessionTree import
packages/opencode/src/cli/cmd/tui/routes/session/header.tsx Complete rewrite for subagent sessions - added sessionPath/depth memos, breadcrumb UI with dynamic truncation, adaptive navigation hints with depth notation
packages/opencode/src/cli/cmd/tui/routes/session/dialog-session-tree.tsx NEW - Session tree dialog component with tree visualization, status indicators, and navigation
packages/sdk/js/src/v2/gen/types.gen.ts Regenerated with new keybind types

Session Navigation Keybinds Reference

Keybind ID Default Binding Action
session_parent <leader>up Go to parent session
session_child_down <leader>down Go to first child session
session_root <leader>escape Go to root session
session_child_cycle <leader>right Next sibling session
session_child_cycle_reverse <leader>left Previous sibling session
session_child_list <leader>s Open session tree dialog
status_view <leader>i View status panel (changed from <leader>s)

Where <leader> defaults to ctrl+x.

Keybind Configuration Note

The default keybinds use <leader>+arrow pattern (where leader defaults to ctrl+x). An alternative worth considering for frequent navigation is ctrl+shift+arrow, which:

  • Reduces key sequence length
  • Avoids accidental interaction with ctrl+c (interrupt) or ctrl+z (suspend)

Users can configure keybinds via opencode.jsonc:

{
  "keybinds": {
    "session_child_down": "ctrl+shift+down",
    "session_parent": "ctrl+shift+up",
    "session_child_cycle": "ctrl+shift+right",
    "session_child_cycle_reverse": "ctrl+shift+left",
    "session_root": "ctrl+shift+escape",
    "session_child_list": "ctrl+shift+s"
  }
}

Testing

Subagent Delegation Tests

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

Unit Tests

  • 6/6 tests passing
  • task_budget configuration parsing
  • permission.task rule evaluation
  • Backwards compatibility

Integration Tests - Full Permission Matrix

Test Configuration:

  • Principal-Partner: Can task Sonnet & Flash only
  • Assistant-Sonnet: Can task Flash only
  • Assistant-Flash: Can task Sonnet only

Results (9/9 tests passing):

Source Agent → Associate → Partner → Sonnet → Flash
Principal-Partner ❌ Denied ✓ ✅ Works ✓ ✅ Works ✓
Assistant-Sonnet ❌ Denied ✓ ❌ Denied ✓ ✅ Works ✓
Assistant-Flash ❌ Denied ✓ ❌ Denied ✓ ✅ Works ✓

Verification: All permissions match configuration exactly. Permission system is fully functional with proper configuration.

Session Persistence & Budget Tests

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

TUI Navigation Tests

Typecheck

  • bun run typecheck - no new errors introduced by these changes (pre-existing errors in unrelated files remain)

Manual Testing - Part 2 (All Verified)

  • Create 4-level hierarchy (Root → L1 → L2 → L3) via delegation chain
  • ctrl+x down navigates to child sessions
  • ctrl+x up returns to parent correctly
  • ctrl+x left/right cycles only siblings at same level
  • ctrl+x esc jumps to root from any depth
  • Clicking task indicator navigates (including text-only returns)
  • Delegation box shows ctrl+x down view subagents hint
  • Breadcrumb displays clickable path and navigates on click
  • Breadcrumb dynamically truncates based on terminal width
  • Navigation hints adapt based on availability (Root only shows at depth ≥ 2)
  • Depth notation labels correct (Parent, Child(L1), Child(L2), etc.)
  • Root session header unchanged

Manual Testing - Part 3 (All Verified)

  • Open tree dialog with <leader>s
  • Open tree dialog via command palette "Session tree"
  • Verify tree structure displays correctly with multi-level hierarchy
  • Navigate with arrow keys + Enter
  • Navigate with mouse click
  • Verify current session shows indicator
  • Verify pending permission shows indicator
  • Verify busy session shows spinner
  • Verify single-root tree (no subagents) shows single row
  • Verify deep nesting (L4+) indentation scales correctly
  • Verify <leader>i opens status panel (keybind migration)

Commits

Subagent Delegation

  1. feat(task): add subagent-to-subagent task delegation - Core implementation with session persistence and budget tracking
  2. fix(task): use strict equality for callable_by_subagents check - Type safety (obsoleted by commit 5)
  3. fix(task): change budget scope from per-message to per-session - Correct budget counting
  4. refactor(task): promote task_budget and callable_by_subagents to first-class config fields - Zod schema support (obsoleted by commit 5)
  5. refactor(task): remove callable_by_subagents flag, use permissions-only approach - Simplifies delegation control using existing permission system
  6. fix(task): enforce permission checks for subagent-to-subagent delegation - Fixes bypassAgentCheck propagation bug

TUI Navigation

  1. feat(tui): add hierarchical session navigation for subagent sessions
  2. feat(tui): add Root navigation hint for deep subagent sessions
  3. feat(tui): add session tree dialog with visual hierarchy and status indicators

Backwards Compatibility

  • No breaking changes: Default behavior is unchanged
  • Opt-in only: Users must explicitly configure task_budget and permission.task rules
  • Built-in agents unaffected: general, explore, etc. retain their existing behavior
  • All TUI changes are additive: Existing keybinds continue to work as before
  • Keybind migration: status_view moved from <leader>s to <leader>i (users can reconfigure if preferred)

Screenshots

TUI-sess-navigation-demo-1 TUI-sess-navigation-demo-bgtask TUI-sess-navigation-tree-viewer

Test Plan

  • Unit tests pass (6/6 tests)
  • Integration tests pass (9/9 permission matrix tests)
  • Typecheck passes (opencode package)
  • Manual testing with isolated configuration
  • Blinded session persistence tests
  • Budget enforcement verification
  • Permission hierarchy verification
  • Frontmatter/JSON config interaction verified
  • TUI navigation manual testing (all scenarios)
  • Session tree dialog manual testing (all scenarios)

@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.

@zackheil
Copy link

I've been playing around with this in my own fork and like it! Couple of things I noticed:

  1. "callable_by_subagents": true feels redundant since you can specify in the permissions for an agent or subagent what all it can call. I think the existing model of allowing a particular agent access to summon a subagent would suffice and having both would create confusion
{
  "agent": {
    "build": {
      "permission": {
        "task": {
          "some-subagent": "allow"
        }
      }
    },
    "some-subagent": {
      "task_budget": 5,
      "mode": "subagent",
      "permission": {
        "task": {
          "some-other-subagent": "allow" // inherently equiv to "callable_by_subagents": true for `some-other-subagent`?
        }
      }
    },
    "some-other-subagent": {
      //...
    }
  }
}
  1. I noticed I couldn't use ctrl+x, right to look at the sub-sub agent's transcript. I could only look at the first subagent's transcript
  2. I think you will need to add the task budget to the json schema file that governs the config

@NamedIdentity
Copy link
Author

NamedIdentity commented Jan 20, 2026

@zackheil

1. callable_by_subagents
The purpose is to allow broad delegation permission while protecting specific agents.

For my workflow I have a Primary agent (Principal) and a specific subagent (Partner) it coworks with. Partner is a subagent with elevated authority. It's basically a sub-primary agent where the orchestrating agent offloads demanding tasks ('substantive work'). So Partner can't be tasked by other subagents, even subagents I might decide get to task any other subagent.

With callable_by_subagents: false (the default), I can:

  • Give one subagent permission to task any other subagent ("task": { "*": "allow" })
  • Still protect Partner from being tasked by that subagent

Without this flag, you'd need to enumerate every allowed target agent in the permission config just to exclude one or two.
That said, I'm open to suggestions if you see a cleaner way to achieve this with existing permissions.

2. ctrl+x
I haven't had any difficulty with subagent session navigation. I can click on the main sessions box for the delegated agent to bring up a subagent session, or use the ctrl+x, right/left.

3. JSON schema
I'm glad you pointed this out. I've updated the PR. task_budget and callable_by_subagents are now first-class config fields in the Zod schema (not in the options catchall), so they'll appear in the auto-generated JSON schema with proper types and validation.

4. Issue #7296
Sorry for prematurely closing the Issue #7296. I'm a total newbie and didn't realize it needed to stay open until the PR got merged.

5. PR Updated
While performing the revisions I rebased the PR with the latest dev build. I'm currently running the rebased PR no problems as my main installation; it did the push to update the fork/PR.

@zackheil
Copy link

zackheil commented Jan 20, 2026

  1. It sounds like you can invert the logic at a global level and then use the task permission in the first agent to get what you want. If primary can call partner, and neither primary and partner are to be called by subagents, you could make the global: task: { "*": "allow", "primary": "deny", "partner": "deny" } and then in primary you just make it: task: { "*": "deny", "partner": "allow" } (assuming you only want the later subagents called through partner). Then primary cannot call the other subagents that go through partner, can only call partner, and partner can call everything but itself and the primary? Then the flag to set the ability to call partner for any other top-level agents is a single line of work that is equivalent to the flag callable flag you added. I hope I understood that use case correctly.
  2. Hm. I was able to see the first subagent's task that was spawned, but <leader>left/right would just cycle between the main session and the first subagent called. It wouldn't show me the subagent's spawned subagent unless I clicked on where it spawned that session in the subagents session. I'll try again, but could be a larger navigation bug that's unrelated.

@NamedIdentity
Copy link
Author

NamedIdentity commented Jan 20, 2026

@zackheil

I took a closer look, and I can confirm you are correct about the sessions of spawned subagents not being selectable. My recollection during testing was in error. My test confirmed that the spawned subagents get called and can persist their sessions, but did so without viewing their session directly.

I think maybe you're also right about being able to use existing permissions. I'll be looking into that further as I work on figuring out if we can make the sessions for spawned subagents selectable.

I'm still getting the hang of running dev installations and having to fix some problems related to that. So, got a long todo list prior to working directly on an update for these issues you've found.

@NamedIdentity
Copy link
Author

@zackheil

You were not only right about callable_by_subagents being redundant, but as I did a more careful analysis it became clear the tag would create inefficiencies in building agentic workflows.

With callable_by_subagents=false, if the user decides they want Partner (an elevated authority subagent acting as a sub-primary agent) to be callable by another subagent, then they'd have to make callable_by_subagents=true. But then any subagents that had "task": {"": allow} can call Partner, and you'd have to add "Principal-Partner: "deny" to all of those subagents configs in order to let just one subagent call Partner. So, the callable_by_subagents tag creates an inefficiency in constructing/developing and adapting workflows that is likely to result in a user having their workflow break and then try to figure out how to fix it.

Thank you Zachheil for helping me figure this out.

I have updated the PR to use the existing permissions system instead of callable_by_subagents. We (me and the AI) found and fixed a few bugs in the permission system, and found some others that I left (but documented) because I think it requires more experienced developers who are familiar with the permissions system. I re-performed my tests, updated the PR description, and have pushed the update to the fork.

I will start working on trying to see what I can do to make the spawned subagent sessions viewable in the TUI.

@NamedIdentity
Copy link
Author

@zackheil

I thought you'd be interested to learn, that child subagent sessions do have viewable/selectable sessions for tasked subagents (granddhildren) in the TUI. But only when the grandchild subagent makes a tool call. If the grandchild subagent only returns text to the tasking subagent, it only shows up as a text string with an icon (no box).

I really thought I remembered seeing this earlier. I'd have probably found it if I had made a more exhaustive search of prior sessions I used for testing.

As I've been building a SPEC to fix this issue, it's become evident that the session navigation system really needs an overhaul. I'll try to see what I can do.

image

@NamedIdentity NamedIdentity changed the title feat(task): Add configurable subagent-to-subagent task delegation with budgets, and persistent sessions feat(task): Add subagent-to-subagent delegation with budgets, persistent sessions, and hierarchical session navigation Jan 23, 2026
@NamedIdentity
Copy link
Author

@zackheil

I fixed the UX issues. You can now navigate subagent sessions fully. You can even view asynchronous subagents called as oh-my-opencode background tasks.

Rebased. Tested. Working. PR description updated.

Thank you for your feedback Zack. The PR is a lot better because of it.

NamedIdentity and others added 5 commits January 23, 2026 21:52
…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>
…t-class config fields

- Add explicit Zod schema definitions for task_budget and callable_by_subagents
- Add fields to Agent.Info runtime type with mapping from config
- Update task.ts to read from top-level agent fields instead of options
- Update tests to use neutral terminology and check top-level fields

This enables JSON Schema validation and editor autocomplete for these config options.
…ly approach

Removes the callable_by_subagents flag in favor of using the existing
permission system for all delegation control. This change improves workflow
flexibility by enabling additive (grant-based) configuration instead of
subtractive (flag + denies) configuration.

Rationale:
- Existing permission system already provides per-caller granularity
- Additive permissions support iterative workflow development better
- Simpler for selective delegation (add one allow vs open flag + block others)
- Less configuration complexity for experimental/evolving agentic workflows

Breaking change: Users previously setting callable_by_subagents must now
use permission rules to control which agents can be tasked by subagents.

Changes:
- config.ts: Remove callable_by_subagents from Agent Zod schema and knownKeys
- agent.ts: Remove callable_by_subagents from Agent.Info type and mapping
- task.ts: Remove callable_by_subagents check (Check 2)
- Tests: Remove 3 tests for callable_by_subagents, update remaining tests

Tests: 6/6 passing
Typecheck: Clean

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
NamedIdentity and others added 4 commits January 23, 2026 21:53
Fixes a critical bug where bypassAgentCheck flag (set when user invokes
agents with @ or when prompt resolution creates agent parts) was propagating
to Task tool calls made BY subagents, causing permission rules to be ignored.

Root cause:
- When Task tool creates a subagent session, resolvePromptParts() may create
  "agent" type parts if prompt contains unresolved {file:...} references
- This triggers bypassAgentCheck=true for the entire subagent session
- All subsequent Task calls by that subagent bypass permission checks

Fix:
- Move isSubagent check before permission check
- Always enforce permissions when caller is a subagent, even if
  bypassAgentCheck is set
- Preserves OpenCode's intended behavior: user @ invocation can bypass,
  but subagent-to-subagent delegation always checks permissions

Impact:
- Subagent permission.task rules now work correctly
- User @ invocation bypass still works (OpenCode behavior preserved)
- Fixes reported issue: assistant-sonnet could task any agent despite
  permission rules denying it

Tests: 6/6 passing
Typecheck: Clean

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add session_child_down keybind (<leader>down) to navigate into child sessions
- Add session_root keybind (<leader>escape) to jump directly to root session
- Fix sibling cycling to only cycle sessions at same hierarchy level
- Fix Task component clickability for text-only subagent returns (use sessionId)
- Rewrite Header component for subagent sessions with 3-row layout:
  - Row 1: Clickable breadcrumb trail with dynamic width-based truncation
  - Row 2: Divider + token/cost/version stats
  - Row 3: Adaptive navigation hints (Parent/Child(Ln) notation)
- Breadcrumb shows agent names for child sessions, full title for root
- Root session header unchanged for familiar UX
Shows 'Root ctrl+x esc' hint when at depth 2+ (L2 or below),
allowing users to jump directly to root session.
…ndicators

- Add DialogSessionTree component with tree visualization (box-drawing chars)
- Add session_child_list keybind (<leader>s) to open session tree dialog
- Move status_view keybind from <leader>s to <leader>i (collision resolution)
- Show status indicators: current (●), awaiting permission (◉), busy (spinner)
- Extract agent names from session titles (@agent pattern or first word)
- Use Locale.time() for timestamps (respects user OS locale)

Related: anomalyco#6183
Adds global level_limit configuration to cap subagent session tree depth.
Complements existing task_budget (horizontal limit) with vertical depth
limit for complete loop prevention.

- Add level_limit to experimental config schema (default: 5)
- Add getSessionDepth() helper to calculate session tree depth
- Add depth check before task delegation (Check 3)
- Add 3 unit tests for level_limit configuration
- Regenerate SDK types with level_limit field

Related to PR anomalyco#7756 (subagent delegation)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE]: Allow configurable subagent-to-subagent task delegation with call limits

2 participants