-
Notifications
You must be signed in to change notification settings - Fork 7.9k
feat(task): Add subagent-to-subagent delegation with budgets, persistent sessions, and hierarchical session navigation #7756
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Conversation
|
Hey! Your PR title Please update it to start with one of:
Where See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: No duplicate PRs found |
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
I've been playing around with this in my own fork and like it! Couple of things I noticed:
{
"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": {
//...
}
}
}
|
a89d332 to
401f996
Compare
|
1. callable_by_subagents 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
Without this flag, you'd need to enumerate every allowed target agent in the permission config just to exclude one or two. 2. ctrl+x 3. JSON schema 4. Issue #7296 5. PR Updated |
|
|
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. |
|
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. |
|
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. |
…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>
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)

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:
InlineToolcomponents instead of clickableBlockToolindicatorsRelated: PR #7756 comment by zackheil - user reported inability to view sub-sub agent transcripts.
Part 1: Subagent-to-Subagent Delegation
Key Features
task_budgetper agent prevents infinite delegation loopspermission.tasksystem for granular delegation rulesWhat Was Fixed
1adf95c5e)callable_by_subagentsflag: Simplified to use permissions-only approach (commit7e3e5a77b)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.jsonto enable subagents to task subagents by setting a task call budget (task_budget) for an agent. Permissions for thetasktool can be set to limit which agents a subagent can task.Architecture & System Design
Delegation Workflow Example
Key workflow patterns enabled:
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:
level_limitcaps the maximum session tree depth (default: 5)Budget resets when:
Configuration Reference
task_budget(Caller Configuration)Controls how many task calls a subagent can make within a single session.
undefinedor0> 0Location:
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:
task_budget > 0AND permission to task the target"*"(all), agent names, wildcardsExample: 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.
undefined0> 0Location:
experimental.level_limit{ "experimental": { "level_limit": 5 } }How it works:
parentIDchaintask_budget(horizontal limit per session) to provide complete loop preventionExample with 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:
Caller has no task budget configured. Set task_budget > 0 on the calling agent to enable nested delegation.Permission denied: task permission for pattern "<agent>"(via existing OpenCode permission system)Task budget exhausted (N/N calls). Return control to caller to continue.Cannot resume session: not a child of caller session. Session "<id>" is not owned by this caller.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:Why?
OpenCode's config system loads markdown files AFTER JSON config. The legacy
toolsfield gets converted topermissionrules, and markdown values override JSON values in conflicts. To use pattern-basedpermission.taskrules (like{ "*": "deny", "agent-x": "allow" }), thetoolsfield must not be present in frontmatter.See:
Artifacts_Agents/permission-bugs-and-fixes.mdfor detailed technical explanation.Bugs Fixed During Development
Bug #1: bypassAgentCheck Propagation (FIXED)
Issue: Permission checks were being bypassed for subagent-to-subagent delegation when
bypassAgentCheckflag was set.Root Cause: The
bypassAgentCheckflag (intended to allow user@agentinvocations) was propagating to Task calls made BY subagents, causing them to bypass permission checks entirely.Fix (commit
1adf95c5e): Modifiedtask.tsto always enforce permissions for subagent-to-subagent delegation, even whenbypassAgentCheckis set. Preserves OpenCode's intended behavior for user@agentinvocations while ensuring subagent delegations always check permissions.Impact: Subagent
permission.taskrules 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:
tools: { task: true }becomespermission.task = "allow"toolsfrom frontmatterSecurity Considerations
Potential Risk: Config Self-Modification
If a subagent is given write permission to config files without manual authorization, it could hypothetically modify
opencode.jsonto 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:
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, rightto look at the sub-sub agent's transcript."Solution
Implement a tree-based navigation model where:
The subagent header is redesigned with three rows:
New Keybinds
session_child_down<leader>downsession_root<leader>escapeNavigation Logic Changes
siblingsmemo: New memo that filters sessions byx.parentID === session()?.parentIDfor accurate sibling cyclingdirectChildrenmemo: New memo that filters sessions byx.parentID === session()?.idfor down navigationmoveChild(direction): Updated to usesiblings()instead ofchildren()for left/right cyclingmoveToFirstChild(): New function to navigate to the first element ofdirectChildren()moveToRoot(): New function that traverses up theparentIDchain to find and navigate to rootHeader Component Rewrite
The subagent header (
header.tsx) was significantly rewritten:sessionPathmemo walks upparentIDchain to build ancestry arraydepthmemo computes hierarchy level (0 = root)Parent(depth 1→root) orChild(Ln)notation (e.g.,Child(L1),Child(L2))Task Component Fix
Changed clickability condition from
props.metadata.summary?.lengthtoprops.metadata.sessionId:session_child_cycletosession_child_downSDK Type Additions
Regenerated SDK types to include new keybinds:
session_child_down?: stringsession_root?: stringoh-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):parentIDpoints to Child, not ParentThis 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>sor via the command palette ("Session tree") to see all sessions and navigate directly to any one.Features
●Current session (primary color)◉Session awaiting permission (warning color)@agent-namepattern) or falls back to first wordUI Example
Keybind Changes
<leader>ssession_child_list<leader>istatus_view<leader>s<leader>iRationale for
status_viewKeybind ChangeWe intentionally moved
status_viewfrom<leader>sto<leader>ifor the following reasons:Collision Resolution: Both
status_viewand the newsession_child_listcannot share<leader>s. One had to move.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
/statuscommand instead.Mnemonic Strength:
<leader>s→ Session tree (primary use case, high frequency)<leader>i→ Info/status (secondary use case, "info" is a common TUI convention)Reserving Prime Keys:
<leader>sis a "prime" keybind (easy to type, strong mnemonic). Allocating it to the more frequently-used feature maximizes user efficiency.Alternative Considered: We evaluated
ctrl+sfor 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>twould be better forstatus_view:/status),<leader>iis appropriate<leader>tcould provide faster muscle-memory access<leader>tis currently used bytheme_listTechnical Notes
DialogSelectcomponent for consistent UI/UXparentIDchainagentfield; agent name extracted from title pattern or first wordLocale.time()to respect user's OS locale settingFiles Changed
Subagent Delegation
packages/opencode/src/tool/task.tspackages/opencode/test/task-delegation.test.tsTUI Session Navigation
packages/opencode/src/config/config.tssession_child_down,session_root, andsession_child_listkeybind schema entries; changedstatus_viewdefault from<leader>sto<leader>ipackages/opencode/src/cli/cmd/tui/routes/session/index.tsxsiblingsanddirectChildrenmemos; addedmoveToFirstChild()andmoveToRoot()functions; registeredsession.child.down,session.root, andsession.treecommands; fixed Task clickability condition; addedDialogSessionTreeimportpackages/opencode/src/cli/cmd/tui/routes/session/header.tsxpackages/opencode/src/cli/cmd/tui/routes/session/dialog-session-tree.tsxpackages/sdk/js/src/v2/gen/types.gen.tsSession Navigation Keybinds Reference
session_parent<leader>upsession_child_down<leader>downsession_root<leader>escapesession_child_cycle<leader>rightsession_child_cycle_reverse<leader>leftsession_child_list<leader>sstatus_view<leader>i<leader>s)Where
<leader>defaults toctrl+x.Keybind Configuration Note
The default keybinds use
<leader>+arrowpattern (where leader defaults toctrl+x). An alternative worth considering for frequent navigation isctrl+shift+arrow, which:ctrl+c(interrupt) orctrl+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:
Unit Tests
task_budgetconfiguration parsingpermission.taskrule evaluationIntegration Tests - Full Permission Matrix
Test Configuration:
Results (9/9 tests passing):
Verification: All permissions match configuration exactly. Permission system is fully functional with proper configuration.
Session Persistence & Budget Tests
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)
ctrl+x downnavigates to child sessionsctrl+x upreturns to parent correctlyctrl+x left/rightcycles only siblings at same levelctrl+x escjumps to root from any depthctrl+x down view subagentshintParent,Child(L1),Child(L2), etc.)Manual Testing - Part 3 (All Verified)
<leader>s●indicator◉indicator<leader>iopens status panel (keybind migration)Commits
Subagent Delegation
feat(task): add subagent-to-subagent task delegation- Core implementation with session persistence and budget trackingfix(task): use strict equality for callable_by_subagents check- Type safety (obsoleted by commit 5)fix(task): change budget scope from per-message to per-session- Correct budget countingrefactor(task): promote task_budget and callable_by_subagents to first-class config fields- Zod schema support (obsoleted by commit 5)refactor(task): remove callable_by_subagents flag, use permissions-only approach- Simplifies delegation control using existing permission systemfix(task): enforce permission checks for subagent-to-subagent delegation- Fixes bypassAgentCheck propagation bugTUI Navigation
feat(tui): add hierarchical session navigation for subagent sessionsfeat(tui): add Root navigation hint for deep subagent sessionsfeat(tui): add session tree dialog with visual hierarchy and status indicatorsBackwards Compatibility
task_budgetandpermission.taskrulesgeneral,explore, etc. retain their existing behaviorstatus_viewmoved from<leader>sto<leader>i(users can reconfigure if preferred)Screenshots
Test Plan