Skip to content

fix(acp): make deepseek-acp usable as a delegation target (read limit + opt-in auto-approve) - #1095

Open
fatedawn wants to merge 2 commits into
spacering-net:mainfrom
fatedawn:fix/deepseek-acp-autonomy
Open

fatedawn wants to merge 2 commits into
spacering-net:mainfrom
fatedawn:fix/deepseek-acp-autonomy

Conversation

@fatedawn

@fatedawn fatedawn commented Nov 4, 2026 •

Copy link
Copy Markdown

TL;DR

A deepseek-acp (DeepSeek Harness) sub-agent cannot be used as a delegation target today. Two independent defects, and neither is reachable from the agent's own settings — so users cannot work around either one.

This PR fixes both. Together they turn deepseek-acp from "spawns fine, then wedges" into a working delegation target — verified end-to-end against a real task.

Symptom Cause Fix
Child hangs in Prompting forever on any file > 2 MiB read_response_limit gave it the 2 MiB cap; it never narrows a read 1 line: add it to the existing long-read exemption
Child parks forever on its first write tool call 0.9.0 has no approval mode; its only options are allow-once / reject-once New opt-in CODEG_AUTO_APPROVE

The problem, precisely

1. Unreadable files park the child permanently

read_response_limit already exempts Antigravity and Kimi Code from FS_MAX_READ_RESPONSE_BYTES, and the comment above it states the reason:

Two agents never narrow a read, so under that limit even a few lines of a file over 2 MiB would fail to read

deepseek-acp 0.9.0 is the third member of exactly that class. It ships the read tool's offset/limit as its own page arguments and asks the host for the whole file, paging the answer itself. So:

  • the "narrow with line/limit" hint never reaches it — the model sees only Invalid params
  • delegation.agent_defaults' mode_id cannot help; there is no such mode
  • it carries no retry, so a read that errors is a read it never recovers from
  • and delegate_to_agent has no timeout, so the parent waits too

Result: one oversized read takes down the child and stalls the parent delegation.

Measured twice in 2026-11 on a 2,650,000-byte single-line bundle (index.js). Both times the child sat in Prompting until cancelled by hand — the second time for over eight hours, across a machine restart.

Note that splitting the read does not help, which is the part that makes this look like a mystery bug: the host checks cumulative bytes, not lines, so limit=500 on a single-line file trips the same error.

2. Every write parks on a card nobody is looking at

deepseek-acp 0.9.0 has no approval mode at all — set_mode, availableModes and currentModeId appear nowhere in the package. Its approval/request handler hard-codes exactly two options:

options: [
  { optionId: "allow-once",  name: "Allow once", kind: "allow_once"  },
  { optionId: "reject-once", name: "Reject",     kind: "reject_once" }
]

There is no "always". So delegation.agent_defaults cannot reach it, and since a delegation child has no tab, nobody sees the card. The first Write/Edit tool call parks indefinitely and the parent waits on it.

This is the deeper of the two problems: even with the read limit fixed, deepseek-acp cannot edit code as a sub-agent, which is most of what a delegation is for.


What this PR changes

read_response_limit — one line

AgentType::Antigravity | AgentType::KimiCode | AgentType::DeepSeek => {
    usize::try_from(FS_MAX_FILE_SIZE_BYTES).unwrap_or(usize::MAX)
}

Same 16 MiB ceiling the other two long-read agents already get. No new mechanism — the exemption and its rationale already exist; DeepSeek simply was not listed.

CODEG_AUTO_APPROVE — opt-in auto-approve

When set, codeg answers session/request_permission itself with the request's allow_once option, and publishes the same ToolApproval record a user's click produces — so the transcript chip and the approval history look identical to a manual approval.

Design choices, deliberately conservative:

  • Opt-in, off by default. Unset, empty, or any value other than 1/true/yes keeps the ordinary card flow byte-for-byte unchanged.
  • Agent-agnostic. The flag is the policy — there is no agent-type list to keep in sync, and no way for a future adapter to silently fall outside it.
  • allow_once only — never allow_always. The latter writes a standing rule into the user's own agent settings. This matches the reasoning PermissionOptionInfo::allow_once_in already documents.
  • Falls through unchanged. A request offering no allow_once option still reaches the card, exactly as today.
  • Answered on the same path a click uses. It is not a special-cased bypass; it resolves the responder and emits the approval record identically.

Verification

Built from v0.42.0 and installed:

Before — a delegated deepseek child asked to read the 2.65 MiB single-line index.js sat in Prompting, logged fs/read_text_file -32602, and was cancelled by hand after eight hours.

After — the same task completes in under two minutes and returns a verified answer (20 PRESALES_* symbols enumerated; the target string absent; cross-checked three ways, including ruling out an encoding artifact).

And the write path, which was previously a guaranteed permanent hang:

task: create _probe_deepseek.txt with three known lines, then read it back
result: file created on disk, contents correct, read-back verified — 18 seconds

No card was ever raised; the log shows [ACP] permission … auto-approved … (option=allow-once, CODEG_AUTO_APPROVE).


Why this is worth a release slot

deepseek-acp is the cheapest capable agent in codeg's target list, which makes it the natural choice for the high-token grunt work — reading files, running tests, routine edits — with a stronger model reserved for hard problems. That is a headline use case for a multi-agent aggregator, and right now it fails on first contact.

Both defects are invisible from the UI: the child spawns with no error, shows Prompting, and never moves. There is nothing the user can do from within codeg — no setting, no mode, no retry. That is the signature of a bug the host has to fix.

The read-limit fix is a one-line addition to an exemption whose stated rationale already names this exact failure mode; it costs nothing and closes a trap. The flag is opt-in and touches no existing path when unset, so it cannot regress current behaviour.


Not addressed here

delegate_to_agent still has no timeout. Any adapter that stalls for any other reason can still hold its parent indefinitely. I left it out deliberately: choosing between a wall-clock cap and a no-progress cap is a policy decision that deserves its own discussion, and burying it inside this PR would make both harder to review.

I am happy to follow up with that as a separate change if you would like.

Two independent defects make a `deepseek-acp` sub-agent unusable as a
delegation target, and neither is fixable from the agent's own settings.

1. `read_response_limit` gave DeepSeek the 2 MiB `fs/read_text_file` cap.
   Like Antigravity and Kimi Code — the two agents already exempted here —
   `deepseek-acp` ships the `read` tool's `offset`/`limit` as its own page
   arguments and asks the host for the WHOLE file, paging the answer itself,
   so the "narrow with line/limit" hint never reaches it and every read of a
   file past 2 MiB fails with `Invalid params` the model cannot act on.
   Measured twice in 2026-11 on a 2.65 MiB single-line bundle: the child sat
   in `Prompting` until cancelled by hand, the second time for over eight
   hours, because `deepseek-acp` carries no retry and codeg's delegation has
   no timeout.

2. There was no way to keep a child off the permission card. `deepseek-acp`
   0.9.0 has no approval MODE at all (`set_mode` / `availableModes` appear
   nowhere in the package) and hard-codes an allow-once / reject-once pair in
   its `approval/request` handler, so a write tool call parked forever with
   its parent delegation waiting on it.

   `CODEG_AUTO_APPROVE` (inherited by every ACP child codeg spawns) now makes
   codeg answer `session/request_permission` itself with the request's
   `allow_once` option, publishing the same `ToolApproval` record a user's
   click does. Deliberately opt-in and agent-agnostic: the flag IS the policy,
   and only `allow_once` is used — never `allow_always`, which would write a
   standing rule into the user's own agent settings. A request offering no
   `allow_once` option still falls through to the card, unchanged.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

When CODEG_AUTO_APPROVE is enabled, the new branch bypasses CLI permission policies and inherited permission ceilings, potentially approving writes the run should deny.

4 open issues
What changed in this PR

Improves DeepSeek delegation in Codeg’s ACP runtime by supporting larger whole-file reads and adding opt-in permission auto-approval.

Changes:

  • Gives DeepSeek the existing 16 MiB whole-file read ceiling.
  • Adds CODEG_AUTO_APPROVE to select allow_once, preserving the card flow when that option is unavailable.
  • Emits approval records identifying automatic decisions with the auto_approve origin.
File Description
src-tauri/​src/​acp/​mod.rs Defines the environment flag parser and automatic-approval origin.
src-tauri/​src/​acp/​file_system_runtime.rs Adds DeepSeek to the larger read-response allowance.
src-tauri/​src/​acp/​connection.rs Handles automatic permission responses and emits approval records.

Review effort: Max

// rule into the user's own agent settings (see
// [`PermissionOptionInfo::allow_once_in`]). A request offering no such
// option falls through to the card exactly as before.
if crate::acp::auto_approve_enabled() {
Comment on lines +3511 to +3513
if crate::acp::auto_approve_enabled() {
if let Some(option_id) =
PermissionOptionInfo::allow_once_in(&card.options).map(|o| o.option_id.clone())
if let Some(option_id) =
PermissionOptionInfo::allow_once_in(&card.options).map(|o| o.option_id.clone())
{
let scope = permission_log_scope(state).await;
tool_call_id,
option_kind: kind,
option_name: name,
origin: crate::acp::AUTO_APPROVE_ORIGIN.to_string(),
@fatedawn fatedawn changed the title fix(acp): raise DeepSeek's read limit and add opt-in auto-approve fix(acp): make deepseek-acp usable as a delegation target (read limit + opt-in auto-approve) Nov 4, 2026
@fatedawn

fatedawn commented Nov 4, 2026

Copy link
Copy Markdown
Author

One more note on why the read fix has to be per-agent rather than a blanket raise.

I considered simply raising FS_MAX_READ_RESPONSE_BYTES for everyone, and rejected it: for an agent that does honour line/limit (claude-agent-acp, codex-acp), the 2 MiB cap is doing real protective work — it stops one careless whole-file read from pushing a couple of megabytes into a model's context. The exemption list is the right shape; it just needs the third member.

The two agents already on it are there for the same reason, and deepseek-acp is not a special case — it is the same class, documented in its own FsClient.

…is a release

`cargo build --release --bin codeg` produced a dev binary: `tauri` was
compiled without `custom-protocol`, so `tauri-build` set `DEP_TAURI_DEV=true`
and the app asked `devUrl` (http://localhost:3000) for a dev server it never
started — the window showed ERR_CONNECTION_REFUSED while the process ran fine.
`pnpm tauri build` turns the feature on implicitly, which is why only manual
builds hit this.

Declaring it here makes `--features custom-protocol` explicit and nameable.
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.

2 participants