本文分析当前工程中一条 Bash 命令从 Tool Body 进入 Shell Service、Sandbox Provider 和 Subprocess Runtime最终成为 Linux 进程并返回结果的完整路径。主要阅读范围包括packages/shell/tool-bash、packages/shell/shell、packages/shell/bash-local、packages/shell/bash-sandbox、packages/sandbox、packages/sandbox/sandbox-policy、packages/subprocess/subprocess与packages/subprocess/subprocess-local。当前代码与 DeepSeek Harness 官方仓库后续版本可能存在差异文中的结论只针对本文读取的版本。上一篇已经沿着一次 Tool Call 分析到ToolDefinition.execute()模型产生的调用先成为tool/call随后经过 Tool Runtime 的准入、执行和结果流水线最后作为tool/result回到 Session。对于 Bash 工具而言Tool Body 并没有直接调用 Node.js 的exec()而是继续把命令交给ctx.shell。Shell 层负责命令语义、默认值、超时与前后台进程抽象Sandbox 层负责把原始 argv 包装为受限执行Subprocess 层则负责环境、stdio、输出保留、进程范围所有权和终止收敛。这条执行链的复杂度主要来自两个事实。第一模型提交的是 Shell 源码而底层进程服务只接受明确的 argv不负责字符串解释第二Agent 取消一次命令时停止顶层 Bash PID 并不足以证明命令已经结束Bash 可能已经启动管道、脚本、编译器或后台子进程。当前实现因此没有把“子进程退出”与“受管理进程范围清空”视为同一个条件也没有把“返回超时结果”与“实际工作已经停止”视为同一件事。1. 命令执行链的分层结构当前 Web 和 Headless 的基础 bundle 默认挂载bash-sandbox作为ctx.shell实现同时挂载sandbox-policy、sandbox-local和subprocess-local。模型调用bash后运行路径可以整理为ToolRuntime - deepseek-ai/dsh-tool-bash - 参数补充、提权审批、工作目录、DSH 环境 - ctx.shell.resolve() - ctx.shell.run() / ctx.shell.start() - deepseek-ai/dsh-bash-sandbox - ctx.sandbox.confine([bash, -c, command], policy) - 受限模式返回 runner argv - danger-full-access 直接使用原始 argv - deepseek-ai/dsh-bash-local - timeout deadline - SubprocessSpawnSpec - ctx.subprocess.spawn() - deepseek-ai/dsh-subprocess-local - 环境清理与 stdio 绑定 - Linux systemd scope 或 detached PGID fallback - 输出 tail / spill - TERM - grace - KILL - managed range quiescence - ShellRunResult / ShellProcess - Bash canonical output - Tool Runtime output schema / render - tool/result这些模块的边界并不是按文件夹机械拆分。tool-bash是模型协议适配器负责 Tool Schema、展示、Session 工作目录、后台 Job 和提权参数ShellExecutor是命令执行能力接口不依赖模型与 SessionLocalBashExecutor将 Shell 请求变成bash -c和 Subprocess SpecSandboxBashExecutor继承本地执行机械只替换 argv 准备和沙箱结果分类SubprocessRuntime只处理完全展开的进程请求不理解 Bash 字符串、Tool Call、Session 或模型输出。2. Bash Tool 的输入契约tool-bash使用defineTool()注册名为bash的工具。模型必须提供command和description还可以提供timeoutMs、workdir与run_in_background。挂载受限执行器时schema 额外公开sandbox_permissions和justification。其中 description 只是 UI 展示信息不参与底层命令command 才会原样成为bash -c的源码。参数 schema 只覆盖结构类型Tool Body 开头的validateBashArgs()继续检查 DSL 无法表达的约束command 和 description 去除空白后必须非空timeout 必须为正有限数sandbox_permissions 与 justification 必须成对出现justification 也不能是空句子。ParameterSchemaSpec的根对象默认开放额外字段因此某个部署没有在 schema 中公开后台或提权字段时执行路径仍会再次检查组合是否真正支持不能把“没有展示给模型”等同于“不可能收到”。Bash Tool 的输出是严格的 union。后台分支只返回{ kind: background, jobId }前台分支返回退出码、终止信号、timeout/abort 分类、有效 timeout、stdout、stderr 和可选 sandbox 信息。Tool Body 返回 canonical JSON value 后上一篇分析过的 Tool Runtime 会再次依据 output schema 校验再由 render 把结构化结果投影为模型看到的文本。因此 Bash 执行层保存的是明确状态[exit code: N]、[timed out after ...]和[killed by signal: ...]只是最后的模型表现不是内部状态的唯一来源。3. Session 工作目录与执行目录resolveWorkdir()首先取得当前 Agent Session 的header.cwd。如果 Sandbox Policy 已经解析出 workspaceRoot则以该 root 为准确保“相对目录基准”和“沙箱允许写入的工作区”使用同一个文件系统身份。模型没有提供 workdir 时直接使用这个 Session 目录模型给出相对路径时把它拼接到 Session 目录下绝对路径则原样交给后续层。这里没有在 Tool 层对路径做realpath()或目录存在性验证。Tool 层的任务只是建立执行请求真正的文件系统解释发生在执行环境中。相对路径也不是靠 Bash 里的cd持久化每个调用都会新建一个 Shell工具描述明确要求通过 workdir 表达目录。LocalBashExecutor.resolve()会为缺失的 workdir 使用配置项cwd再退回process.cwd()但正常 Agent 调用通常已经由 Session 提供目录。这种设计保留了 Shell 调用的无状态性。一次命令中的cd、变量、函数和 shell option 都只属于当前bash -c不会进入下一次 Tool Call。需要跨调用保存的事实必须进入文件、Session、Job 或其他明确的持久结构而不是依赖一个隐含的长期 Shell 进程。4. Sandbox Policy 的逐调用解析SandboxPolicyService是文件副作用策略的统一来源。它保存部署默认 mode 与 fallback workspaceRoot并通过 Session Projection 读取最后一条sandbox/mode事件。每次命令执行时策略按以下优先级形成一次性已批准 mode Session 中最后记录的 sandbox/mode 部署默认 modeworkspaceRoot 则优先取 Session Header 中不可变的 cwd没有 Session 或 Session 没有 cwd 时才使用部署 fallback。解析结果是完整的SandboxExecutionPolicy包含 mode、绝对 workspaceRoot 和可选 sessionId。Bash Executor 不需要认识 Sessiontool-bash在操作边界把当前 Session 交给 Policy Service再把得到的纯数据 policy 放入 Shell 请求。当前 mode 包括read-only、workspace-write和danger-full-access。前两个属于 confined modeSandboxBashExecutor会请求ctx.sandbox生成受限 argvdanger-full-access不再调用 confinement provider直接走本地 Bash 机械但仍在前台结果中报告{ mode, denied: false }使调用方能够知道实际选择的策略。5. 一次性 Sandbox 提权模型在真实 denial 之后可以用同一条 command 加上sandbox_permissions和 justification 重试。Tool Body 在任何命令执行之前调用approveEscalation()先判断目标 mode 是否相对当前有效 mode 严格变宽再检查 Approval Service 和 Agent 是否存在最后把目标 mode 与 justification 组成可审计理由交给用户审批。非严格变宽的请求不会弹出审批通道缺失、无人可问、用户拒绝、交互取消和不可用通道全部 fail closed。批准结果只覆盖这一次 Shell 请求。代码通过{ ...standingPolicy, mode: approvedMode }构造 per-call policy没有写入sandbox/modeSession Event也没有修改部署默认值。下一次命令仍从 Session 和部署配置重新解析 standing policy。这与权限预设切换的语义不同前者是一个 Tool Call 的临时例外后者是 Session 后续操作共享的状态。Sandbox denial 也不是 Tool Runtime 层面的isError。命令确实启动并由受限 runner 返回结果SandboxBashExecutor根据退出状态和 stderr signature 设置sandbox.deniedrender 再追加统一 marker。模型因此可以区分普通非零退出与文件策略拒绝并按工具说明进行一次受审批的原命令重试。Sandbox runner 自身不可用则属于基础设施故障前台路径抛出SandboxUnavailableError后台路径在 process sandbox facts 中标记runnerFailed不能把 runner 故障误报成命令被策略拒绝。6. Shell Request 的默认值与上限LocalBashExecutor.resolve()把开放的ShellExecRequest转成完全展开的ShellExecSpec。默认前台 timeout 为 120 秒配置允许的 per-call timeout 上限为 600 秒模型给出的值会经过clampTimeout()不是无限制接受。stdout 默认内存预算为 64,000 bytesstderr 使用相同 executor 配置上限每个 stream 的默认 spill 上限为 64 MiBTERM 到 KILL 的默认 grace 为 3 秒。resolve 以后run 和 start 不再重复读取默认值。这一点让同一调用即使跨过异步 sandbox 准备也不会在中途因热更新配置而拼出前后不一致的 spec。ShellExecSpec还保留 stdin、普通 env、Harness 管理的 dshEnv 和 sandboxPolicy模型面对的 Bash Tool 不公开 stdin 与任意 env其他可信插件可以通过 Shell Service 使用这两个能力。前台 stdout 可以由可信调用方单独申请stdoutMaxBytesstderr 始终使用 executor 的普通输出上限。模型 Bash Tool 没有暴露这个参数避免模型任意扩大宿主内存预算。后台 start 明确忽略 timeoutMs生命周期改由 Job 取消信号和ShellProcess.kill()管理。7. 子进程环境的组合顺序Shell 层先加入NO_COLOR1、TERMdumb、PAGERcat和GIT_PAGERcat减少颜色控制符、分页器和交互终端行为对 Tool Result 的污染。普通调用方 env 覆盖这些 model-friendly 默认值当前调用收集到的 dshEnv 最后合并因此 Harness 管理的DSH_*事实不能被普通 env 冒充。Subprocess 层不会直接继承完整的process.env。scrubbedParentEnv()删除名称匹配KEY|PASSWORD|SECRET|TOKEN的凭据形环境变量并删除所有环境中的DSH_*PATH、HOME、locale 和代理变量保留。显式 spec.env 在 scrub 以后合并所以可信调用方仍可以有意识地恢复某个值undefined则作为 tombstone 删除普通 ambient entry。Windows 合并按环境变量名大小写不敏感处理POSIX 使用通常的区分大小写语义。这层清理防止 Harness 自身的模型 API Key、Session 身份和旧 DSH 上下文无意进入命令。与此同时代理设置会按父进程已经解析的策略重新覆盖并为子 Node 进程带上使用环境代理所需的选项避免宿主经过代理而子进程悄悄直连。8. 原始 Bash argv 与受限 argv未启用 confinement 时前台run()和后台start()都使用精确 argv[bash, -c, command]Subprocess Service 接受的是 argv 数组不会再次把数组拼成 Shell 字符串。真正进行 Shell 语法解析的只有bash -c因此重定向、管道、变量展开和 heredoc 都发生在 Bash 内部而不是 Node spawn 或 Subprocess Runtime 中。SandboxBashExecutor在 confined mode 下调用ctx.sandbox.confine([bash, -c, command], policy, signal)。Provider 返回ConfinedArgv其中既有真正交给 Subprocess 的 runner argv也有 enforcement 完整度、denial signatures、runner failure rules 等结算分类事实。前台 sandbox 准备与命令执行共享同一个 deadline准备阶段已经超时会返回尚未 spawn 的 timedOut 结果外部 Abort 则继续作为取消抛出。danger-full-access不经过 runnerconfined mode 则必须返回能够实际实施限制的 argv或失败。代码不允许 sandbox provider 静默退回未受限的原始命令。具体 Linux backend 可以选择适合宿主的平台机制但上层只消费统一的 ConfinedArgv 与结算事实。9. 前台命令的 Deadline 与原因分类LocalBashExecutor.runArgv()使用deadline(spec.signal, spec.timeoutMs, BASH_TIMEOUT)把调用方 AbortSignal 与 executor timeout 合并成一个信号。这个信号同时覆盖异步 sandbox 准备和后续 Subprocess。准备函数与 deadline 竞争只有本层拥有的BASH_TIMEOUT首先触发时尚未 spawn 的调用才返回timedOut: true、空输出和 null exit facts其他取消原因继续向上抛出。命令已经 spawn 后Subprocess Runtime 只响应 signal 并开始终止不解释取消原因。handle.donesettle 后Bash Executor 再通过timeoutOf(d.signal, BASH_TIMEOUT)判断是否是本层 timeout如果 fused signal 已 abort 但没有本层 timeout code则记为aborted。两者互斥竞争发生时以第一个 Abort 原因作为最终分类。Tool Body 在拿到ShellRunResult后对普通非零退出、timeout 和信号终止都返回成功的 canonical foreground value只有result.aborted会重新抛出 code 为ABORTED的 HarnessError使 Tool Runtime 将本次调用标成 isError。普通 timeout 会由 render 输出 marker但不是工具协议失败。这样的区分允许模型阅读已产生的 stdout/stderr、退出状态和 timeout 事实而用户主动取消则沿 Agent 取消路径闭合。10. Subprocess Spec 与显式 stdiospawnSpec()把 Shell Spec 转换成无默认值的SubprocessSpawnSpec。argv、cwd、stdin、stdout、stderr、grace、signal 和 env 全部明确指定。模型 Bash 调用没有 stdin 时使用ignore底层 fd 0 对应空输入可信调用方提供 stdin 字符串时使用 batch modeSubprocess 启动后写入并关闭。stdout 与 stderr 分别进入 collect mode而不是交给exec()一次性积累在内部 Buffer 中。Subprocess seam 还支持原始pipe、父进程inherit和独立 control channel但 Bash Tool 使用的是 bounded collect。SubprocessHandle.done只包含 exitCode 和 signal不包含 timeout、取消与输出cause classification 属于持有 deadline 的 Bash 层输出则通过handle.collected的 offset reader读取。这种拆分使批量命令和后台增量读取可以共用同一个 Collector。Subprocess Spec 在同步入口检查 grace、预取消状态和 argv[0]。预取消会在创建 handle 之前直接拒绝空程序名与非法 grace 也不会进入系统 spawn。SubprocessRuntime 不对命令设置默认工作目录或输出预算避免多个消费者共享一套隐藏进程策略。11. 有界内存输出与 Spill 文件OutputCollector为 stdout 和 stderr 各维护一个有界内存尾部。新 chunk 到达后collector 记录完整流的累计 byte offset超过 maxBytes 时从头部丢弃整 chunk 或精确裁掉头部字节始终保留最后 maxBytes。保留尾部是有意选择因为错误、编译结论和最终统计通常靠近命令输出末尾。配置 spill 时第一次内存溢出会在 OS 临时目录下的私有目录中创建随机文件使用 exclusive create 和0600权限并把此前已收集的 chunk 与后续 chunk 写入。只要完整输出没有超过 maxSpillBytes这个文件就是完整流一旦超过 spill 上限文件会关闭并删除后续只保留内存尾部防止“内存有界但磁盘无限”。close 失败也会停止公开 spill path因为文件尾部可能不可靠。前台结算通过readFrom(0)读取保留内容offset 已滑出窗口时把 truncated 设为 true并在存在可靠 spill 时返回路径。render 会在输出尾部添加[output truncated; full output: ...]。后台读取维护独立 stdoutOffset 与 stderrOffset每次只返回上次位置之后的 delta如果调用者轮询太慢导致 offset 滑出内存窗口read 标记 lossy并给出可用 spill 文件。offset 属于读取者而不是 Collector 内部游标因此多个可信消费者可以独立读取不会互相吃掉输出。12. stdout、stderr 与退出状态的模型呈现前台 render 先放 stdout再在需要时追加[stderr]区段。两者都为空时输出(no output)。Sandbox denial、timeout、signal 和非零 exit code 作为 marker 追加exit marker保持最后供 UI presenter 解析为 terminal card 的 exit pill。非零 exit code 不会把 Tool Result 标成 isError。命令已经按请求正常启动并返回退出码属于命令领域结果模型需要依据 stderr 和 exit marker继续判断。基础设施错误才以异常进入 Tool Runtime例如 spawn 失败、sandbox runner 不可用或调用被 Agent 取消。信号终止也保留在结构化 ShellRunResult 中timeout 即使被命令 trap 并最终 exit 0timedOutmarker 仍然存在避免被表面退出码覆盖。Sandbox denial同样独立于 exit code。Provider 使用自己返回的 denial signature 和 runner failure rules 对 stderr 进行分类runner failure 优先于 denial因为 runner 没有成功执行内部命令。不同 sandbox backend 的诊断文本可以不同上层不把某个固定 Linux 错误字符串写死成全局判断。13. Linux Managed Range 的优先路径LocalSubprocessRuntime在 Linux 上优先探测 native managed range。探测要求私有 runner 可用、libcexecve绑定可加载并且当前用户的 systemd manager 支持所需的 transient scope 调用。深度探测成功后会缓存正向结果后续仍用轻量 manager probe确认 user manager可达条件不满足时才选择 fallback并只警告一次当前进程使用了更弱的进程树约束。native 路径通过systemd-run --user --scope --quiet --collect --expand-environmentno创建唯一 transient scope。私有 runner使用文件传递完整 cwd、env 和 control 配置进入 scope 后消费 launch request最终以execve替换自己并运行目标 argv。--expand-environmentno与文件化环境避免 systemd 对 argv 或环境做第二次意外展开。这个 scope 是命令的 managed range。终止时SystemdScopeOwner调用systemctl --user kill --kill-whomall --signal... unit不是只向systemd-run客户端或顶层 Bash 发信号。waitForExit()轮询 unit 的 LoadState、ActiveState 和 TasksCurrent只有 scope 不再活动或已证明为空才完成。启动尚未完全建立时还保留 direct process/group fallback处理取消与 bootstrap 建立之间的竞态。systemd scope 的价值不只在于发送信号范围更大还在于提供独立的可观察所有权。顶层命令退出后只要 scope 中仍有成员Subprocess Runtime 仍能保留 handle 并在 teardown 时终止它们。普通父子 PID 关系会因 double-fork、reparent 或 Shell 退出而改变scope/cgroup 的成员关系更适合作为“这次命令仍然拥有多少工作”的判断依据。14. Detached PGID FallbackLinux native prerequisites 不可用、运行在 macOS、Windows 或测试指定 fallback 时普通 spawn 走spawnSubprocess()。POSIX 设置detached: true使子进程成为新 process group leadersignal 使用负 pid 发送给整个 process group失败时再尝试 direct child。Windows 不使用 detached group而是通过taskkill /PID pid /T /F终止进程树。fallback owner 的waitForExit()通过 process group 存活探测等待范围为空Linux 在 direct child 已 settle 后还会检查/proc中是否存在 live group member避免僵尸 group 表象阻塞。尽管这比只等待 ChildProcess exit 更完整源码仍明确把它称为 weaker containment脱离原 process group 或逃出可观察父树的后代不保证被终止也不保证延迟 waitForExit。文章分析当前 Linux 机制时不能把 fallback 的process.kill(-pid, ...)当成全部实现。15. TERM、KILL 与真实收敛bindManagedProcess()把 platform launch 与公共 SubprocessHandle 生命周期绑定。spec.signal abort 或调用terminate()时代码只允许第一次进入 termination立即启动 managed range observation先发送 SIGTERM并设置 grace timergrace 到期仍未证明 range 消失时再发送 SIGKILL。Windows owner 对任意阶段都执行强制 tree termination但接口仍保持同一语义。顶层 ChildProcess 的 direct outcome 与 managed range exit 是两条不同 Promise。handle.done在 direct process exit 并且收集管道结束后 resolve继承 stdout/stderr fd 的幸存后代可能让 pipe 保持打开因此还设有同一个 graceMs 的 pipe drain边界避免结果永久不 settle。该边界只销毁 Harness 自己的 collect streamraw pipe属于调用者不能被收集器擅自关闭。即使 direct process 已经 settleSIGKILL escalation timer也不会立即清除因为 managed range 中可能仍有后代。只有 owner 的waitForExit()确认范围为空才取消后续 escalation 并移除 abort listener。这正是“工具 Promise 已返回”“顶层 Bash 已退出”和“本次命令拥有的全部工作已停止”之间的区别。16. Runtime 卸载与宿主退出LocalSubprocessRuntime持有所有 live ordinary handles 与 terminal handles。正常 Cordis disposal 时它先对每个普通 handle调用 terminate同时等待handle.done和handle.waitForExit()terminal 则调用自身的 awaited terminate。全部结果通过Promise.allSettled()收集一个对象清理失败不会阻止其他对象开始终止最后再合并抛出失败。Node 的同步exit阶段无法 await因此 Runtime 还注册terminateForHostExit()。该路径直接对每个仍受控的范围执行最终强制终止systemd owner 同时尝试 direct SIGKILL 和 unit-wide SIGKILLfallback owner执行 process group或 taskkill。它不声称已经等待 quiescence只是宿主退出前的最后兜底。正常 disposal才是能够承诺等待 managed range 清空的主路径。后台进程之所以能穿过 Shell Executor 自身热重载而继续存在也来自所有权位于 Subprocess Runtime。ShellProcess只是对 SubprocessHandle 的适配真正 live set 由ctx.subprocess保留只有 Subprocess Service 的 composition teardown才会统一停止并等待这些进程。17. 后台命令与 Job 所有权转移模型设置run_in_background: true时Bash Tool 不直接用当前 Tool Call signal 启动长期进程。它先确认后台能力和 Jobs Service 可用并检查 Tool Call尚未取消然后调用jobs.start()。Job 的 run callback 才获得 Job 自己的 AbortSignal并以该 signal完成ctx.shell.resolve()和ctx.shell.start()。当 jobs.start返回 id 时长期工作的取消所有权已经从 Tool Call 转给 Job。processJob()内部还有一个 controller用于覆盖“Job 已取消但 ShellProcess 仍在异步准备”的窗口。取消发生在 handle 发布前controller使准备 reject发布后则额外调用process.kill()。done Promise会等待准备与底层 process.done完整收敛然后把 ShellProcess 状态映射为 JobOutcome。非零退出仍属于 completeddetail保存 exit codesignal kill属于 killed准备阶段真正失败才是 failed。后台readOutput()是消费式的增量视图连续job_output不重复返回旧内容。stderr仍用[stderr]分段lossy读取附带完整 spill pathsandbox runner failure 与 denial marker也在 process settle后进入输出。后台启动 Tool Result本身只包含 job id不把尚未完成的命令伪装成一次已完成 Bash 结果。18. Shell、Terminal 与 Persistent Bash 的边界本文分析的是一次性 Bash Tool。它始终创建新bash -cstdin默认关闭TERMdumb也不分配 PTY。需要持续会话、交互式程序、前台 process group inspection 或发送控制字符时应当使用 Terminal/PTY能力而不是把一次性 Bash Tool扩展成隐式持久 Shell。Subprocess seam为此另外定义spawnTerminal()本地实现使用 node-pty并管理完整 OS session、terminal output、resize、foreground group signal和 awaited termination。tool-bash-persistent建立在另一条持久 Shell抽象上。两种路径与本文的 ordinarySubprocessSpawnSpec有共同的环境与 managed range理念但 stdio、信号目标和所有权契约不同不能把普通 pipe subprocess 的行为直接套到 PTY。19. 命令结果的语义分层当前实现至少区分五层结果阅读日志和设计新工具时需要保持这种分层层级结果形态主要含义OS processexitCode / signal顶层进程的直接退出事实Subprocessoutcome collected readers进程结果、输出与 managed range 生命周期ShelltimedOut / aborted / sandbox调用方 deadline 与执行策略分类Bash canonical valueforeground / background unionTool output schema校验的结构化值Tool Resultcontent / isError / meta模型和 Session 消费的规范结果非零 exit code 在 OS、Subprocess、Shell 和 Bash 层都是正常可描述结果Tool Result仍可成功。spawn ENOENT、Sandbox runner failure等基础设施错误会抛出并成为 isError。Bash executor timeout通常形成带 marker的成功 Tool ResultAgent取消则形成 ABORTED error。把这些状态压成一个 boolean success会丢失模型继续诊断所需的信息也会让回放无法区分命令问题、策略拒绝和运行基础设施问题。20. Linux 专用工具的实现边界未来实现 systemd、Docker、进程和网络工具时可以复用 Subprocess Service但不应全部退化成bash字符串模板。专用工具应当用结构化参数表达目标和动作用 canonical output保存 exit facts之外的领域信息并根据副作用声明并发安全性。需要调用 CLI 时可以构造明确 argv直接交给 Subprocess避免再经过bash -c的字符串解释需要 Shell语法时才使用 Shell Service。Sandbox Policy、Approval和managed range仍然适用。一次 systemctl restart需要明确的权限与审计Docker build可能产生大输出需要 bounded collect和spill日志跟随属于长期任务应该转给Job或Terminal所有权任何启动外部进程的工具都必须传递 AbortSignal并等待底层工作真实收敛。专用 Tool只是收窄模型协议不能绕过现有执行边界。21. 最终结论DeepSeek Harness 的命令执行机制不是一层 Shell 包装而是一条逐级收紧的能力链。Bash Tool负责模型参数、Session身份、工作目录、一次性提权和前后台选择Shell Executor负责默认值、deadline、环境和命令结果Sandbox Executor把原始bash -cargv替换成受限 runner argv并分类 denialSubprocess Runtime负责无凭据泄漏的环境、显式 stdio、有界输出、spill文件和可终止的进程范围Linux本地实现优先用user-systemd transient scope建立可观察所有权不可用时才退回detached process group。取消语义贯穿了整条链。Tool Call signal进入Shell deadlinedeadline signal进入Subprocess SpecSubprocess abort触发managed range的TERM到KILL升级Bash层在进程settle后判断首个取消原因Tool Runtime再把Agent取消收口成规范结果。任何一层都没有用“先返回一条错误后台进程以后再说”的方式伪造完成。对于能够修改文件、启动服务和管理系统资源的Linux Agent这种对真实收敛的坚持比单纯执行成功更重要。完成命令执行链之后下一步可以进入第一项Linux专用能力。合适的起点不是另一个自由文本命令工具而是只读的系统状态采集通过结构化接口返回操作系统、CPU、内存、磁盘、网络和服务状态再比较它与通用Bash在参数约束、输出稳定性、权限审计和模型可用性上的差异。