Repository navigation
fix: preserve custom agents and edit permissions during delegation - #14866
Conversation
The agent_manager tool had no way to say which agent should run a session it starts. The tool schema, bus event, and extension-side task parser all lacked an agent field, and both session.create and the initial promptAsync were called without one. Server-side that falls through to agents.defaultInfo(), so every tool-started Agent Manager session ran the workspace default agent. Custom agents carry their own permission rules, and those rules are merged onto that named agent only. A workspace that denies edit globally and re-allows paths inside a custom agent therefore lost the allowances: Permission.disabled() stripped write/edit/apply_patch from the tools sent to the model, which surfaced as "Model tried to call unavailable tool 'write'". The task tool and the user-driven worktree dialog already propagate a chosen agent; only the tool-started path dropped it. Thread an optional agent through the tool schema, bus event, extension ToolTask/parser, session.create, and the initial prompt. Validate it in the CLI tool so an unknown name or a mode "subagent" agent fails before any permission prompt or session creation, instead of silently running a different agent. Omitting agent keeps the previous default-agent behavior. Extract the session.create body into session-create.ts so both the local and worktree paths share it and AgentManagerProvider.ts stays within its line cap.
Code Review SummaryStatus: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (9 files)
|
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (3 files)
Incremental review of The prior SUGGESTION (that a custom delegator's path-scoped edit denies, e.g. Previous Review Summaries (4 snapshots, latest commit 0c8849c)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 0c8849c)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (4 files)
Incremental review of Note: the PR description only covers the agent-manager Fix these issues in Kilo Cloud Previous review (commit e703b56)Status: No Issues Found | Recommendation: Merge Files Reviewed (4 files)
Incremental review of Previous review (commit 4fcf08b)Status: No Issues Found | Recommendation: Merge Files Reviewed (4 files)
Previous review (commit f01b273)Status: 1 Issue Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
Files Reviewed (9 files)
Reviewed by deepseek-v4.1-flash · Input: 70.1K · Output: 3.5K · Cached: 225.5K Review guidance: REVIEW.md from base branch |
The agent name was validated after trim() but forwarded untrimmed, so a
padded-but-valid name like " candidate " passed the fail-fast check and
then failed later at prompt time with "Agent not found": resolveKey only
remaps build to code and never trims, so agents.get(" candidate ") misses.
That is the late-failure class the fail-fast check exists to prevent.
Trim once at the publish site so the canonical name is what propagates,
which also drops a whitespace-only agent instead of publishing an empty
string. Canonicalize at the extension parser too, since handleToolEvent
to parseToolRequest is a separate trust boundary that gated on trim()
truthiness but assigned the raw value. Extracted into an agent() helper
mirroring model(), keeping task() within its complexity limit.
Validation checked mode but not hidden, so the built-in utility agents (title, summary, compaction) passed: they are mode "primary" with hidden true. Passing agent: "title" was accepted and would have created a real session, and in worktree mode a real branch and directory, running a deny-only agent whose prompt only generates titles. The webview picker cannot even display it, since filterVisibleAgents strips hidden agents, and Agent.defaultInfo already refuses them. The selectable list was also only attached to the "not found" branch. A plausible wrong guess resolves instead, so general or explore hit the subagent branch and returned no list at all, which made the most likely wrong answers the least recoverable. The caller cannot enumerate agents from the tool schema or its context, so every rejection now carries the list and the field description says so. Two of the tests asserted on an array filled by subscribeCallback, which cannot fail: that callback runs on its own fiber and is still pending at assertion time. They now wait on a queue, and were confirmed to fail when validation is bypassed.
Keep a custom delegator's edit restrictions local to that agent instead of stripping its child's configured write tools. Preserve explicit session lockdowns and native read-only ceilings, including across multiple delegation hops. Cover scoped child permissions and actual foreground/background file writes with explicit and catch-all parent denials. Keep other inherited notebook, MCP, board, and resume restrictions unchanged.
A custom delegator's whole edit ruleset stops at that agent, including
path-scoped denies such as `edit: { "secrets/**": "deny" }`, not just a
catch-all denial. State that in the changeset and the inherited()
doc comment, and add a regression test asserting a delegator's scoped
deny does not confine its subagent while a session-level denial still
does. Verified with task-edit-permissions (12 pass) and the 7-file
permission suite (201 pass).
What Problem This Solves
A customer on Kilo Code 7.8.3 reported that a custom agent could write when run directly, but its delegated session failed with
Model tried to call unavailable tool 'write'; neitherwritenoreditappeared in the child's available-tools list. Local checks with Swarm enabled/disabled did not reproduce it.This PR now addresses the shared CLI
taskpermission-inheritance mechanism that reproduces that failure, as well as the adjacent named-agent selection gap in VS Code'sagent_managertool. These are separate launch paths, not interchangeable explanations for the report.Why This Change Was Made
Customer Findings
The initial investigation had only ingest logs and attributed the report to
agent_managerfalling back to the default agent. After obtaining session-viewer access, we retrieved all three actual session records and transcripts, which disproved that attribution for this incident. Sharing remained disabled; no customer transcripts, documents, credentials, or exports are attached here.job-scouttaskcareer-consultantwriteis unavailable and the error's tool list omits bothwriteandeditcareer-consultantwritesuccessfullyThe primary's recorded invocation names
subagent_type: "career-consultant"and setsbackground: true. The child runs that exact agent, not the workspace default. Therefore the originalagent_managerpropagation change alone could not fix the customer's recorded execution path. The evidence does not establish a Swarm-specific cause.Reproduced Mechanism
KiloTask.inheritedcombined the caller agent's policy with parent-session restrictions and copied mutation denies onto the child session. It also synthesized anedit/* denywhen the caller's explicit or catch-all policy denied editing. The child's session policy is merged after its own agent policy;Permission.disabledmapswriteandapply_patchtoedit, so this ceiling removes all three tools even when the selected child's scoped configuration allows writing.Using the reported permission-map shape, we reproduced: direct custom writer works; delegation from an edit-allowing parent works; delegation from an edit-denying custom parent loses its write tools. Real prompt/tool/filesystem regression tests reproduced the failure with the old behavior and write the allowed file after the fix, in both foreground and background execution.
The actual parent permission configuration is still unavailable. The authorized trace API returns messages and metadata, not the raw effective session/agent ruleset. The fix addresses a demonstrated inheritance mechanism matching the symptoms; it is not a claim that we reconstructed or replayed every customer setting.
Changes:
native === false) no longer turns its own edit restriction into a child-session ceiling. The selected child's edit policy governs its writes.agent_managerchanges remain: carry the optional agent through session creation and the first prompt, trim names, reject unknown/subagent-only/hidden names before starting anything, and return selectable names on rejection.User Impact
Read-only custom delegators can hand editing work to explicitly configured writable subagents without those children losing
write/edit. This avoids the reproduced failed-tool turn and subsequent fallback work; no token-savings benchmark or blanket guarantee is claimed.This intentionally changes custom-agent inheritance semantics: a custom agent's own
edit: denyrestricts that agent, not all its descendants. Use explicit session restrictions to lock the delegated workflow. Built-in Plan, Ask, and Orchestrator restrictions remain unchanged, and the child's own prohibited paths and approval requirements remain enforced.Separately, VS Code model-started Agent Manager sessions can select a named visible primary/
allagent instead of always using the default. Omittingagentretains the old default behavior; padded valid names are normalized, not rejected. Pure subagents still belong in thetaskpath.Screenshots / Video
N/A - no visual changes.
Evidence
All commands below were run by the agent, not reported as human verification.
From
packages/opencode/, this passed 217 tests, 0 failures:KILO_CLIENT=cli bun test ./test/kilocode/task-edit-permissions.test.ts ./test/kilocode/session-prompt-permission-refresh.test.ts ./test/kilocode/agent-permission-overrides.test.ts ./test/agent/plan-mode-subagent-bypass.test.ts ./test/kilocode/task-nesting.test.ts ./test/tool/task.test.ts ./test/kilocode/agent-manager-tool.test.ts ./test/kilocode/tool-registry-indexing.test.tsFrom
packages/kilo-vscode/, this passed 135 tests, 0 failures:bun test tests/unit/agent-manager-tool-start.test.ts tests/unit/agent-manager-arch.test.ts tests/unit/agent-manager-tool-project.test.ts tests/unit/multi-version.test.ts tests/unit/provider-multi-version.test.tsbun run typecheckin both packages, plus extensionbun run lintandbun run knip: passed.bunx oxlint packages/opencode/src/kilocode/tool/task.ts packages/opencode/test/kilocode/task-edit-permissions.test.ts packages/opencode/test/kilocode/session-prompt-permission-refresh.test.ts: 0 errors; 9 warnings on unchanged pre-existing declarations/assertions.bunx prettier --check packages/opencode/src/kilocode/tool/task.ts packages/opencode/test/kilocode/task-edit-permissions.test.ts,git diff --check, andbun run script/check-opencode-annotations.ts --worktree: passed. Unrelated formatting in the existing integration test was left unchanged.bunx changeset status --since=origin/main: valid; the fixed CLI/extension release group includes both packages.Coverage includes four real prompt/tool/filesystem cases (foreground/background crossed with explicit/catch-all parent denial), child scoped allow/deny/ask, session-wide and path-specific locks, Plan restrictions through a custom intermediary, native Plan/Ask/Orchestrator, unknown provenance, and retained MCP/notebook/board ceilings. The deterministic local test LLM exercises actual delegation and file writes; it is not a live remote model.
Before the fix, the custom-delegator unit regressions failed. Restoring the old edit source also made both initial end-to-end foreground/background cases fail; restoring the fix made them pass. Earlier Agent Manager validation tests were likewise mutation-checked. Two asynchronous no-publication tests were corrected to await a queue rather than assert on a callback-filled array before that callback could execute.
Dependency
No SDK regeneration, API migration, or prerequisite PR is needed. The delegation fix is already in the shared CLI; no JetBrains-specific copy or extraction is required.
JetBrains delivery requires a released CLI containing this fix and a pin update. The inspected plugin is pinned to CLI 7.8.3. Both IDEs use the same
taskpermission logic with the same CLI/configuration, but merging this source does not update an installed pinned binary.JetBrains' human-created worktree flow already forwards a selected agent with a nonempty initial prompt. Empty prepared-session selection remains a separate limitation. Model-driven visible-session orchestration is also separate:
agent_manageris currently exposed only to VS Code, and JetBrains has no matching request/event bridge. This PR does not add that bridge or claim feature parity for that tool.Issue
Customer support report; no linked public GitHub issue was identified. The three private sessions were inspected through an authorized read-only session-viewer API, without changing sharing settings or accessing production databases directly.
Context
The shared
taskpath already selected the correct agent in the customer trace. Its inherited permission ceiling, rather than its agent selection, is the reproduced problem addressed by the latest commit.The original
agent_managerchanges address an independently verified gap: its schema/event/extension parser lacked a named-agent field. That path creates visible top-level sessions, whereastaskcreates parent-linked children. The description has been corrected to stop presenting the former as the customer's confirmed cause.Implementation
For edit inheritance, both explicit-deny collection and effective wildcard-deny synthesis use session-only policy when the caller is definitively custom. Native and unknown-provenance callers retain the previous combined policy. Updating only the explicit rules would be insufficient: a caller's catch-all deny could still synthesize the missing edit ceiling. Other inherited capabilities remain on the existing path.
For managed sessions, the optional agent persists during creation even without an initial prompt. Validation excludes hidden utility agents as well as subagent-only agents; otherwise
title/summary/compactioncould run as visible sessions. Trimmed validation and publication now agree. No new managed-session escalation guard was added: the existingagent_managerpermission check still gates starts, and sessions that omit the field remain unchanged.Proactive
agent_manageragent discovery is not implemented. It has a plain string field, not an injected selectable catalog; the user's named agent and rejection responses remain its discovery/recovery paths. Thetasktool's advertised subagent list is not the same eligible set. Returning valid names on all rejection branches improves recovery but does not eliminate the extra round trip.How to Test
Manual/local verification
The agent inspected all three customer transcripts, checked the relevant inheritance code in both the 7.8.3 source and this branch, and exercised production permission helpers with the customer's rule shape. Automated verification and actual fixture-file writes are listed above. Customer exports remain private and are not included in the commit.
Reviewer test steps
job-scoutwithpermission: { "edit": "deny", "task": "allow" }and acareer-consultantsubagent withedit: { "*": "deny", "candidate/profile.md": "allow", "candidate/_uploads/*": "deny" }.candidate/profile.mdthroughtask, first foreground and thenbackground: true. Confirm the child receiveswrite/editand creates the allowed file.agent_managerfeature, use a visible primary/allagent name and confirm selection persists on local/worktree starts, including prepared sessions. Unknown, hidden, and subagent-only names must fail before approval/session creation and list eligible agents; whitespace-padded valid names must succeed.Blocked checks and substitute verification
bun run testinvocation in the extension was blocked by baselineTS6059cross-rootDirerrors incompile-tests. Directbun test, package typechecks/lint, and Knip are passing substitutes. Generated artifacts from that invocation were removed.Checklist
Get in Touch
N/A