Skip to content

fix: preserve custom agents and edit permissions during delegation - #14866

Merged
kirillk merged 5 commits into
mainfrom
clever-canyon
Oct 15, 2026
Merged

kirillk merged 5 commits into
mainfrom
clever-canyon

Conversation

@kirillk

@kirillk kirillk commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

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'; neither write nor edit appeared in the child's available-tools list. Local checks with Swarm enabled/disabled did not reproduce it.

This PR now addresses the shared CLI task permission-inheritance mechanism that reproduces that failure, as well as the adjacent named-agent selection gap in VS Code's agent_manager tool. 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_manager falling 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.

Trace Recorded agent Observed behavior
Primary, 41 messages job-scout Launches background children using task
Affected child, 9 messages career-consultant Parent linkage matches the primary; write is unavailable and the error's tool list omits both write and edit
Successful direct session, 7 messages career-consultant Calls write successfully

The primary's recorded invocation names subagent_type: "career-consultant" and sets background: true. The child runs that exact agent, not the workspace default. Therefore the original agent_manager propagation change alone could not fix the customer's recorded execution path. The evidence does not establish a Swarm-specific cause.

Reproduced Mechanism

KiloTask.inherited combined the caller agent's policy with parent-session restrictions and copied mutation denies onto the child session. It also synthesized an edit/* deny when the caller's explicit or catch-all policy denied editing. The child's session policy is merged after its own agent policy; Permission.disabled maps write and apply_patch to edit, 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:

  • A confirmed custom caller (native === false) no longer turns its own edit restriction into a child-session ceiling. The selected child's edit policy governs its writes.
  • Explicit parent-session lockdowns and native/unknown caller edit ceilings remain enforced, including through a custom intermediate agent.
  • Child allow/ask/deny rules remain intact; parent allows are not transplanted over child denials. MCP, notebook, board, bash, sandbox, depth, and resume behavior are not relaxed by this change.
  • The existing named-agent agent_manager changes 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: deny restricts 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/all agent instead of always using the default. Omitting agent retains the old default behavior; padded valid names are normalized, not rejected. Pure subagents still belong in the task path.

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

From 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.ts
  • bun run typecheck in both packages, plus extension bun run lint and bun run knip: passed.
  • Root 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.
  • Root bunx prettier --check packages/opencode/src/kilocode/tool/task.ts packages/opencode/test/kilocode/task-edit-permissions.test.ts, git diff --check, and bun 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.
  • Pre-push Turbo typechecks: passed, including a cached JetBrains typecheck. This is not a JetBrains runtime verification of the new CLI behavior.

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 task permission 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_manager is 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 task path 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_manager changes address an independently verified gap: its schema/event/extension parser lacked a named-agent field. That path creates visible top-level sessions, whereas task creates 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/compaction could run as visible sessions. Trimmed validation and publication now agree. No new managed-session escalation guard was added: the existing agent_manager permission check still gates starts, and sessions that omit the field remain unchanged.

Proactive agent_manager agent 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. The task tool'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

  1. Configure a custom primary job-scout with permission: { "edit": "deny", "task": "allow" } and a career-consultant subagent with edit: { "*": "deny", "candidate/profile.md": "allow", "candidate/_uploads/*": "deny" }.
  2. Have the primary delegate creation of candidate/profile.md through task, first foreground and then background: true. Confirm the child receives write/edit and creates the allowed file.
  3. Repeat with a catch-all parent deny plus a task allowance. Confirm the child still uses its own edit policy and cannot write prohibited paths.
  4. Apply an explicit session edit denial, or delegate from built-in Plan. Confirm that writes remain unavailable. A custom intermediary must not remove the inherited session lock.
  5. For the separate VS Code agent_manager feature, use a visible primary/all agent 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

  • No live VS Code/JetBrains extension-host replay or remote-model token benchmark was performed. The new behavior is verified through actual CLI prompt/tool/filesystem execution with a deterministic local model server.
  • Customer parent permissions were not returned by the trace API. A session-level lockdown remains a valid reason for a child's writes to be denied, even with this change.
  • Existing resumed children may retain old persisted edit ceilings. Start a new child to exercise the fix; deleting unknown historical denials without provenance would be unsafe.
  • Scoped parent-session allow exceptions still follow the existing deny-only inheritance behavior. This PR does not implement general permission-set intersection.
  • The earlier bun run test invocation in the extension was blocked by baseline TS6059 cross-rootDir errors in compile-tests. Direct bun test, package typechecks/lint, and Knip are passing substitutes. Generated artifacts from that invocation were removed.

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described and separated from unverified customer configuration
  • Screenshots/video included for visual changes, or marked N/A
  • Changesets included for named-agent support, agent validation, and custom-delegator edit permissions
  • Diff reviewed, including an independent read-only safety review of the delegation change

Get in Touch

N/A

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.
@kilo-code-bot

kilo-code-bot Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/tool/agent-manager.ts 233 The new agent value is validated with trim() (line 431) but forwarded untrimmed, so a whitespace-padded but valid name passes the fail-fast check and later fails at prompt time with Agent not found.
Files Reviewed (9 files)
  • .changeset/agent-manager-custom-agent-field.md - no issues
  • packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts - no issues
  • packages/kilo-vscode/src/agent-manager/session-create.ts - no issues
  • packages/kilo-vscode/src/agent-manager/tool-start.ts - no issues
  • packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts - no issues
  • packages/opencode/src/kilocode/agent-manager/event.ts - no issues
  • packages/opencode/src/kilocode/tool/agent-manager.ts - 1 issue
  • packages/opencode/src/kilocode/tool/agent-manager.txt - no issues
  • packages/opencode/test/kilocode/agent-manager-tool.test.ts - no issues

Fix these issues in Kilo Cloud

@kilo-code-bot

kilo-code-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (3 files)
  • .changeset/custom-delegator-edit-permissions.md
  • packages/opencode/src/kilocode/tool/task.ts
  • packages/opencode/test/kilocode/task-edit-permissions.test.ts

Incremental review of 0c8849c1..039f7314 (single docs commit docs(cli): spell out dropped custom-delegator edit rules). The only code-path change since the last review is none — KiloTask.inherited is untouched; the commit clarifies the doc comment and changeset, and adds one regression test.

The prior SUGGESTION (that a custom delegator's path-scoped edit denies, e.g. edit: { "secrets/**": "deny" }, no longer bound its subagents and was worth stating explicitly) is now addressed: the changeset names the consequence and points users to a session-level edit restriction, and the KiloTask.inherited doc comment matches. The new test a custom delegator's path-scoped edit denial does not confine its subagent documents the semantic change and asserts the child still writes its allowed path.

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

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/opencode/src/kilocode/tool/task.ts 122 For a config-declared (native === false) caller, edit becomes session, so the caller's entire edit ruleset — including path-scoped denies like edit: { "secrets/**": "deny" }, not just a catch-all — is excluded from the inherited ceilings. Only session-scoped edit denials remain. This matches the updated doc comment and tests, so it appears intentional; consider documenting in the changeset that a custom delegator's path-scoped edit denies no longer bound its subagents.
Files Reviewed (4 files)
  • .changeset/custom-delegator-edit-permissions.md
  • packages/opencode/src/kilocode/tool/task.ts - 1 issue
  • packages/opencode/test/kilocode/session-prompt-permission-refresh.test.ts
  • packages/opencode/test/kilocode/task-edit-permissions.test.ts

Incremental review of e703b5637b..0c8849c18d (single commit fix(cli): preserve custom subagent edit permissions). Verified the discriminator input.caller.native === false maps to config-declared agents (agent.ts:344, agent.ts:435) while all built-ins are native: true, so native read-only modes and the plan→custom→child chain keep their edit ceilings (session edit denials are preserved and re-synthesized via the catch-all loop). The added tests are load-bearing and the new live test exercises the real task tool path.

Note: the PR description only covers the agent-manager agent field and two changesets; the third changeset (custom-delegator-edit-permissions.md) and this permission-model change are not described there, which makes the security-relevant intent harder to review.

Fix these issues in Kilo Cloud

Previous review (commit e703b56)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • .changeset/agent-manager-agent-validation.md
  • packages/opencode/src/kilocode/tool/agent-manager.ts
  • packages/opencode/src/kilocode/tool/agent-manager.txt
  • packages/opencode/test/kilocode/agent-manager-tool.test.ts

Incremental review of 4fcf08b5ec..e703b5637b: rejects hidden/internal agents (matching Agent.defaultInfo and the extension picker) and now includes the selectable agent list on every rejection. Verified agents.get returns hidden agents and agents.list includes them, so both the info.hidden check and the !item.hidden filter are necessary. The added continue statements are required now that a third branch follows. No new issues found.

Previous review (commit 4fcf08b)

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • packages/kilo-vscode/src/agent-manager/tool-start.ts
  • packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts
  • packages/opencode/src/kilocode/tool/agent-manager.ts
  • packages/opencode/test/kilocode/agent-manager-tool.test.ts

Previous review (commit f01b273)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/opencode/src/kilocode/tool/agent-manager.ts 233 The new agent value is validated with trim() (line 431) but forwarded untrimmed, so a whitespace-padded but valid name passes the fail-fast check and later fails at prompt time with Agent not found.
Files Reviewed (9 files)
  • .changeset/agent-manager-custom-agent-field.md - no issues
  • packages/kilo-vscode/src/agent-manager/AgentManagerProvider.ts - no issues
  • packages/kilo-vscode/src/agent-manager/session-create.ts - no issues
  • packages/kilo-vscode/src/agent-manager/tool-start.ts - no issues
  • packages/kilo-vscode/tests/unit/agent-manager-tool-start.test.ts - no issues
  • packages/opencode/src/kilocode/agent-manager/event.ts - no issues
  • packages/opencode/src/kilocode/tool/agent-manager.ts - 1 issue
  • packages/opencode/src/kilocode/tool/agent-manager.txt - no issues
  • packages/opencode/test/kilocode/agent-manager-tool.test.ts - no issues

Fix these issues in Kilo Cloud


Reviewed by deepseek-v4.1-flash · Input: 70.1K · Output: 3.5K · Cached: 225.5K

Review guidance: REVIEW.md from base branch main

Comment thread packages/opencode/src/kilocode/tool/agent-manager.ts Outdated
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.
Comment thread packages/opencode/src/kilocode/tool/agent-manager.ts Outdated
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.
@github-actions github-actions Bot added area:agent-manager Agent Manager area:cli CLI, TUI and core engine area:vscode VS Code extension community Opened by someone outside the Kilo team needs-review and removed community Opened by someone outside the Kilo team needs-review labels Oct 8, 2026
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.
@kirillk kirillk changed the title feat(agent-manager): run a named agent for tool-started sessions fix: preserve custom agents and edit permissions during delegation Oct 14, 2026
Comment thread packages/opencode/src/kilocode/tool/task.ts
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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:agent-manager Agent Manager area:cli CLI, TUI and core engine area:vscode VS Code extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants