Przeglądaj źródła

fix(acp-agent): enable default filesystem suite

Yichen Jiang 3 miesięcy temu
rodzic
commit
1cb63c7a18
22 zmienionych plików z 766 dodań i 59 usunięć
  1. 2 10
      examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml
  2. 2 10
      examples/acp-agent/code-mode-workspace-context.cordis.yml
  3. 9 0
      examples/acp-agent/composition.md
  4. 13 0
      examples/acp-agent/cordis.yml
  5. 2 10
      examples/acp-agent/fs.cordis.snapshot.yml
  6. 1 11
      examples/acp-agent/fs.cordis.yml
  7. 33 0
      examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md
  8. 75 0
      examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.golden.json
  9. 33 0
      examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md
  10. 75 0
      examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.golden.json
  11. 33 0
      examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md
  12. 12 0
      examples/acp-agent/tests/snapshots/model-switching/system-prompt.golden.md
  13. 150 0
      examples/acp-agent/tests/snapshots/model-switching/tool-schemas.golden.json
  14. 12 0
      examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md
  15. 150 0
      examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.golden.json
  16. 6 0
      examples/acp-agent/tests/snapshots/skill-load/system-prompt.golden.md
  17. 75 0
      examples/acp-agent/tests/snapshots/skill-load/tool-schemas.golden.json
  18. 6 0
      examples/acp-agent/tests/snapshots/text-turn/system-prompt.golden.md
  19. 75 0
      examples/acp-agent/tests/snapshots/text-turn/tool-schemas.golden.json
  20. 0 8
      examples/acp-agent/workspace-context.cordis.snapshot.yml
  21. 0 9
      examples/acp-agent/workspace-context.cordis.yml
  22. 2 1
      packages/examples/acp-demo/README.md

+ 2 - 10
examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml

@@ -1,5 +1,5 @@
-# Keyless replay counterpart of code-mode-workspace-context.cordis.yml. It
-# enables the filesystem entries needed by this scenario and swaps in replay.
+# Keyless replay counterpart of code-mode-workspace-context.cordis.yml. It adds
+# Code Mode to the default filesystem suite and swaps in replay.
 - id: base
   name: '@cordisjs/plugin-include'
   config:
@@ -23,14 +23,6 @@
 
             Verify your work by running the code or tests. Keep answers brief and factual.
       - insert:
-          - id: fs-local
-            name: '@deepseek-ai/dsh-fs-local'
-            config:
-              cwd: !!js process.cwd()
-          - id: fs-policy
-            name: '@deepseek-ai/dsh-fs-policy'
-          - id: tool-fs
-            name: '@deepseek-ai/dsh-tool-fs'
           - id: code-runtime
             name: '@deepseek-ai/dsh-code-runtime-worker'
           - id: llm-replay

+ 2 - 10
examples/acp-agent/code-mode-workspace-context.cordis.yml

@@ -1,5 +1,5 @@
-# Code Mode workspace-context snapshot recording overlay. The scenario needs
-# filesystem tools to trigger nested instruction discovery after a read.
+# Code Mode workspace-context snapshot recording overlay. The default filesystem
+# tools trigger nested instruction discovery after a read.
 - id: base
   name: '@cordisjs/plugin-include'
   config:
@@ -20,13 +20,5 @@
 
             Verify your work by running the code or tests. Keep answers brief and factual.
       - insert:
-          - id: fs-local
-            name: '@deepseek-ai/dsh-fs-local'
-            config:
-              cwd: !!js process.cwd()
-          - id: fs-policy
-            name: '@deepseek-ai/dsh-fs-policy'
-          - id: tool-fs
-            name: '@deepseek-ai/dsh-tool-fs'
           - id: code-runtime
             name: '@deepseek-ai/dsh-code-runtime-worker'

+ 9 - 0
examples/acp-agent/composition.md

@@ -18,6 +18,12 @@ flowchart LR
   cfg --> plugin_acp_approval
   plugin_acp_permission["permission<br/>@deepseek-ai/dsh-permission"]
   cfg --> plugin_acp_permission
+  plugin_acp_fs_local["fs-local<br/>@deepseek-ai/dsh-fs-local"]
+  cfg --> plugin_acp_fs_local
+  plugin_acp_fs_policy["fs-policy<br/>@deepseek-ai/dsh-fs-policy"]
+  cfg --> plugin_acp_fs_policy
+  plugin_acp_tool_fs["tool-fs<br/>@deepseek-ai/dsh-tool-fs"]
+  cfg --> plugin_acp_tool_fs
   plugin_acp_acp_agent["acp-agent<br/>@deepseek-ai/dsh-acp-demo"]
   cfg --> plugin_acp_acp_agent
   plugin_acp_acp_agent --> bundle_agent_core["@deepseek-ai/dsh-agent-spine-demo"]
@@ -58,6 +64,9 @@ flowchart LR
 | `bash` | `@deepseek-ai/dsh-bash-sandbox` |
 | `approval` | `@deepseek-ai/dsh-user-approval` |
 | `permission` | `@deepseek-ai/dsh-permission` |
+| `fs-local` | `@deepseek-ai/dsh-fs-local` |
+| `fs-policy` | `@deepseek-ai/dsh-fs-policy` |
+| `tool-fs` | `@deepseek-ai/dsh-tool-fs` |
 | `acp-agent` | `@deepseek-ai/dsh-acp-demo` |
 | `subagent` | `@deepseek-ai/dsh-subagent` |
 | `subagent-spawn` | `@deepseek-ai/dsh-subagent-spawn` |

+ 13 - 0
examples/acp-agent/cordis.yml

@@ -30,6 +30,19 @@
 - id: permission
   name: '@deepseek-ai/dsh-permission'
 
+# Workspace instructions and the model-facing read/write/edit tools share the
+# local filesystem provider. fs-policy adds observed-version guards; fs-local's
+# cwd is only a resolution default, not containment, so the permission preset
+# above still confines bash only.
+- id: fs-local
+  name: '@deepseek-ai/dsh-fs-local'
+  config:
+    cwd: !!js process.cwd()
+- id: fs-policy
+  name: '@deepseek-ai/dsh-fs-policy'
+- id: tool-fs
+  name: '@deepseek-ai/dsh-tool-fs'
+
 # The ACP server app: the agent-spine-demo spine + JSONL persistence + the ACP bridge.
 # Persistence root: $DSH_SNAPSHOT_SESSIONS_ROOT when the snapshot harness sets it
 # (so it can harvest / isolate the log), else ./.sessions for the demo.

+ 2 - 10
examples/acp-agent/fs.cordis.snapshot.yml

@@ -1,5 +1,5 @@
-# Keyless filesystem snapshots apply the filesystem and replay overlays directly
-# because include patches cannot target entries behind a nested include.
+# Keyless filesystem snapshots add low spill thresholds and the replay adapter
+# directly because include patches cannot target entries behind a nested include.
 - id: base
   name: '@cordisjs/plugin-include'
   config:
@@ -9,14 +9,6 @@
         name: '@deepseek-ai/dsh-llm-deepseek'
         disabled: true
       - insert:
-          - id: fs-local
-            name: '@deepseek-ai/dsh-fs-local'
-            config:
-              cwd: !!js process.cwd()
-          - id: fs-policy
-            name: '@deepseek-ai/dsh-fs-policy'
-          - id: tool-fs
-            name: '@deepseek-ai/dsh-tool-fs'
           - id: spill-local
             name: '@deepseek-ai/dsh-spill-local'
             config:

+ 1 - 11
examples/acp-agent/fs.cordis.yml

@@ -1,20 +1,10 @@
-# Filesystem snapshots need the in-process local provider, policy gate, and
-# model-facing tools. This explicit overlay is always full-access: the session
-# permission preset controls bash only and cannot confine or unmount these plugins.
+# Filesystem snapshots add low spill thresholds to the default filesystem suite.
 - id: base
   name: '@cordisjs/plugin-include'
   config:
     path: ./cordis.yml
     patches:
       - insert:
-          - id: fs-local
-            name: '@deepseek-ai/dsh-fs-local'
-            config:
-              cwd: !!js process.cwd()
-          - id: fs-policy
-            name: '@deepseek-ai/dsh-fs-policy'
-          - id: tool-fs
-            name: '@deepseek-ai/dsh-tool-fs'
           - id: spill-local
             name: '@deepseek-ai/dsh-spill-local'
             config:

+ 33 - 0
examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md

@@ -5,6 +5,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.
@@ -61,6 +67,26 @@ declare const tools: {
     /** The dynamic mount id returned by cordis_mount (e.g. "dyn-1"). */
     id: string;
   }): Promise<string>;
+  /** Edit an existing UTF-8 text file by replacing literal text. */
+  edit(args: {
+    /** Path to edit, resolved by the filesystem backend. */
+    file_path: string;
+    /** Literal text to replace. Must match exactly. */
+    old_string: string;
+    /** Literal replacement text. Use an empty string to delete the match. */
+    new_string: string;
+    /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */
+    replace_all?: boolean;
+  }): Promise<string>;
+  /** Read a UTF-8 text file and return line-numbered content. */
+  read(args: {
+    /** Path to read, resolved by the filesystem backend. */
+    file_path: string;
+    /** 1-based first line to return. Defaults to 1. */
+    offset?: number;
+    /** Maximum number of lines to return. Defaults to 2000. */
+    limit?: number;
+  }): Promise<string>;
   /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */
   skill(args: {
     /** The exact skill name from the available skills list. */
@@ -139,5 +165,12 @@ declare const tools: {
     /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {"files": [...]}). */
     args?: Record<string, unknown>;
   }): Promise<string>;
+  /** Create or fully replace a UTF-8 text file. */
+  write(args: {
+    /** Path to write, resolved by the filesystem backend. */
+    file_path: string;
+    /** Full UTF-8 text content to write. */
+    content: string;
+  }): Promise<string>;
 }
 ```

+ 75 - 0
examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.golden.json

@@ -102,6 +102,60 @@
         ]
       }
     },
+    {
+      "name": "edit",
+      "description": "Edit an existing UTF-8 text file by replacing literal text.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to edit, resolved by the filesystem backend."
+          },
+          "old_string": {
+            "type": "string",
+            "description": "Literal text to replace. Must match exactly."
+          },
+          "new_string": {
+            "type": "string",
+            "description": "Literal replacement text. Use an empty string to delete the match."
+          },
+          "replace_all": {
+            "type": "boolean",
+            "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+          }
+        },
+        "required": [
+          "file_path",
+          "old_string",
+          "new_string"
+        ]
+      }
+    },
+    {
+      "name": "read",
+      "description": "Read a UTF-8 text file and return line-numbered content.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to read, resolved by the filesystem backend."
+          },
+          "offset": {
+            "type": "number",
+            "description": "1-based first line to return. Defaults to 1."
+          },
+          "limit": {
+            "type": "number",
+            "description": "Maximum number of lines to return. Defaults to 2000."
+          }
+        },
+        "required": [
+          "file_path"
+        ]
+      }
+    },
     {
       "name": "run_code",
       "description": "Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.",
@@ -344,6 +398,27 @@
           "meta"
         ]
       }
+    },
+    {
+      "name": "write",
+      "description": "Create or fully replace a UTF-8 text file.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to write, resolved by the filesystem backend."
+          },
+          "content": {
+            "type": "string",
+            "description": "Full UTF-8 text content to write."
+          }
+        },
+        "required": [
+          "file_path",
+          "content"
+        ]
+      }
     }
   ],
   "changes": []

+ 33 - 0
examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md

@@ -5,6 +5,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.
@@ -44,6 +50,26 @@ declare const tools: {
     /** Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access. */
     justification?: string;
   }): Promise<string>;
+  /** Edit an existing UTF-8 text file by replacing literal text. */
+  edit(args: {
+    /** Path to edit, resolved by the filesystem backend. */
+    file_path: string;
+    /** Literal text to replace. Must match exactly. */
+    old_string: string;
+    /** Literal replacement text. Use an empty string to delete the match. */
+    new_string: string;
+    /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */
+    replace_all?: boolean;
+  }): Promise<string>;
+  /** Read a UTF-8 text file and return line-numbered content. */
+  read(args: {
+    /** Path to read, resolved by the filesystem backend. */
+    file_path: string;
+    /** 1-based first line to return. Defaults to 1. */
+    offset?: number;
+    /** Maximum number of lines to return. Defaults to 2000. */
+    limit?: number;
+  }): Promise<string>;
   /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */
   skill(args: {
     /** The exact skill name from the available skills list. */
@@ -122,5 +148,12 @@ declare const tools: {
     /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {"files": [...]}). */
     args?: Record<string, unknown>;
   }): Promise<string>;
+  /** Create or fully replace a UTF-8 text file. */
+  write(args: {
+    /** Path to write, resolved by the filesystem backend. */
+    file_path: string;
+    /** Full UTF-8 text content to write. */
+    content: string;
+  }): Promise<string>;
 }
 ```

+ 75 - 0
examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.golden.json

@@ -45,6 +45,60 @@
         ]
       }
     },
+    {
+      "name": "edit",
+      "description": "Edit an existing UTF-8 text file by replacing literal text.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to edit, resolved by the filesystem backend."
+          },
+          "old_string": {
+            "type": "string",
+            "description": "Literal text to replace. Must match exactly."
+          },
+          "new_string": {
+            "type": "string",
+            "description": "Literal replacement text. Use an empty string to delete the match."
+          },
+          "replace_all": {
+            "type": "boolean",
+            "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+          }
+        },
+        "required": [
+          "file_path",
+          "old_string",
+          "new_string"
+        ]
+      }
+    },
+    {
+      "name": "read",
+      "description": "Read a UTF-8 text file and return line-numbered content.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to read, resolved by the filesystem backend."
+          },
+          "offset": {
+            "type": "number",
+            "description": "1-based first line to return. Defaults to 1."
+          },
+          "limit": {
+            "type": "number",
+            "description": "Maximum number of lines to return. Defaults to 2000."
+          }
+        },
+        "required": [
+          "file_path"
+        ]
+      }
+    },
     {
       "name": "run_code",
       "description": "Execute a TypeScript program against the available tools. Write the BODY of an async function (erasable syntax only; top-level `await` and `return` work) and call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return comes back — curate it.",
@@ -287,6 +341,27 @@
           "meta"
         ]
       }
+    },
+    {
+      "name": "write",
+      "description": "Create or fully replace a UTF-8 text file.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to write, resolved by the filesystem backend."
+          },
+          "content": {
+            "type": "string",
+            "description": "Full UTF-8 text content to write."
+          }
+        },
+        "required": [
+          "file_path",
+          "content"
+        ]
+      }
     }
   ],
   "changes": []

+ 33 - 0
examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md

@@ -5,6 +5,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.
@@ -44,6 +50,26 @@ declare const tools: {
     /** Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access. */
     justification?: string;
   }): Promise<string>;
+  /** Edit an existing UTF-8 text file by replacing literal text. */
+  edit(args: {
+    /** Path to edit, resolved by the filesystem backend. */
+    file_path: string;
+    /** Literal text to replace. Must match exactly. */
+    old_string: string;
+    /** Literal replacement text. Use an empty string to delete the match. */
+    new_string: string;
+    /** Replace all matches. Defaults to false; when false, old_string must appear exactly once. */
+    replace_all?: boolean;
+  }): Promise<string>;
+  /** Read a UTF-8 text file and return line-numbered content. */
+  read(args: {
+    /** Path to read, resolved by the filesystem backend. */
+    file_path: string;
+    /** 1-based first line to return. Defaults to 1. */
+    offset?: number;
+    /** Maximum number of lines to return. Defaults to 2000. */
+    limit?: number;
+  }): Promise<string>;
   /** Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill. */
   skill(args: {
     /** The exact skill name from the available skills list. */
@@ -122,5 +148,12 @@ declare const tools: {
     /** Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {"files": [...]}). */
     args?: Record<string, unknown>;
   }): Promise<string>;
+  /** Create or fully replace a UTF-8 text file. */
+  write(args: {
+    /** Path to write, resolved by the filesystem backend. */
+    file_path: string;
+    /** Full UTF-8 text content to write. */
+    content: string;
+  }): Promise<string>;
 }
 ```

+ 12 - 0
examples/acp-agent/tests/snapshots/model-switching/system-prompt.golden.md

@@ -5,6 +5,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.
@@ -23,6 +29,12 @@ You are a coding assistant powered by the deepseek-v4-pro model. Your working di
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.

+ 150 - 0
examples/acp-agent/tests/snapshots/model-switching/tool-schemas.golden.json

@@ -45,6 +45,60 @@
         ]
       }
     },
+    {
+      "name": "edit",
+      "description": "Edit an existing UTF-8 text file by replacing literal text.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to edit, resolved by the filesystem backend."
+          },
+          "old_string": {
+            "type": "string",
+            "description": "Literal text to replace. Must match exactly."
+          },
+          "new_string": {
+            "type": "string",
+            "description": "Literal replacement text. Use an empty string to delete the match."
+          },
+          "replace_all": {
+            "type": "boolean",
+            "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+          }
+        },
+        "required": [
+          "file_path",
+          "old_string",
+          "new_string"
+        ]
+      }
+    },
+    {
+      "name": "read",
+      "description": "Read a UTF-8 text file and return line-numbered content.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to read, resolved by the filesystem backend."
+          },
+          "offset": {
+            "type": "number",
+            "description": "1-based first line to return. Defaults to 1."
+          },
+          "limit": {
+            "type": "number",
+            "description": "Maximum number of lines to return. Defaults to 2000."
+          }
+        },
+        "required": [
+          "file_path"
+        ]
+      }
+    },
     {
       "name": "skill",
       "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
@@ -271,6 +325,27 @@
           "meta"
         ]
       }
+    },
+    {
+      "name": "write",
+      "description": "Create or fully replace a UTF-8 text file.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to write, resolved by the filesystem backend."
+          },
+          "content": {
+            "type": "string",
+            "description": "Full UTF-8 text content to write."
+          }
+        },
+        "required": [
+          "file_path",
+          "content"
+        ]
+      }
     }
   ],
   "changes": [
@@ -320,6 +395,60 @@
           ]
         }
       },
+      {
+        "name": "edit",
+        "description": "Edit an existing UTF-8 text file by replacing literal text.",
+        "parameters": {
+          "type": "object",
+          "properties": {
+            "file_path": {
+              "type": "string",
+              "description": "Path to edit, resolved by the filesystem backend."
+            },
+            "old_string": {
+              "type": "string",
+              "description": "Literal text to replace. Must match exactly."
+            },
+            "new_string": {
+              "type": "string",
+              "description": "Literal replacement text. Use an empty string to delete the match."
+            },
+            "replace_all": {
+              "type": "boolean",
+              "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+            }
+          },
+          "required": [
+            "file_path",
+            "old_string",
+            "new_string"
+          ]
+        }
+      },
+      {
+        "name": "read",
+        "description": "Read a UTF-8 text file and return line-numbered content.",
+        "parameters": {
+          "type": "object",
+          "properties": {
+            "file_path": {
+              "type": "string",
+              "description": "Path to read, resolved by the filesystem backend."
+            },
+            "offset": {
+              "type": "number",
+              "description": "1-based first line to return. Defaults to 1."
+            },
+            "limit": {
+              "type": "number",
+              "description": "Maximum number of lines to return. Defaults to 2000."
+            }
+          },
+          "required": [
+            "file_path"
+          ]
+        }
+      },
       {
         "name": "skill",
         "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
@@ -546,6 +675,27 @@
             "meta"
           ]
         }
+      },
+      {
+        "name": "write",
+        "description": "Create or fully replace a UTF-8 text file.",
+        "parameters": {
+          "type": "object",
+          "properties": {
+            "file_path": {
+              "type": "string",
+              "description": "Path to write, resolved by the filesystem backend."
+            },
+            "content": {
+              "type": "string",
+              "description": "Full UTF-8 text content to write."
+            }
+          },
+          "required": [
+            "file_path",
+            "content"
+          ]
+        }
       }
     ]
   ]

+ 12 - 0
examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md

@@ -5,6 +5,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.
@@ -22,6 +28,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.

+ 150 - 0
examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.golden.json

@@ -45,6 +45,60 @@
         ]
       }
     },
+    {
+      "name": "edit",
+      "description": "Edit an existing UTF-8 text file by replacing literal text.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to edit, resolved by the filesystem backend."
+          },
+          "old_string": {
+            "type": "string",
+            "description": "Literal text to replace. Must match exactly."
+          },
+          "new_string": {
+            "type": "string",
+            "description": "Literal replacement text. Use an empty string to delete the match."
+          },
+          "replace_all": {
+            "type": "boolean",
+            "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+          }
+        },
+        "required": [
+          "file_path",
+          "old_string",
+          "new_string"
+        ]
+      }
+    },
+    {
+      "name": "read",
+      "description": "Read a UTF-8 text file and return line-numbered content.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to read, resolved by the filesystem backend."
+          },
+          "offset": {
+            "type": "number",
+            "description": "1-based first line to return. Defaults to 1."
+          },
+          "limit": {
+            "type": "number",
+            "description": "Maximum number of lines to return. Defaults to 2000."
+          }
+        },
+        "required": [
+          "file_path"
+        ]
+      }
+    },
     {
       "name": "skill",
       "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
@@ -271,6 +325,27 @@
           "meta"
         ]
       }
+    },
+    {
+      "name": "write",
+      "description": "Create or fully replace a UTF-8 text file.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to write, resolved by the filesystem backend."
+          },
+          "content": {
+            "type": "string",
+            "description": "Full UTF-8 text content to write."
+          }
+        },
+        "required": [
+          "file_path",
+          "content"
+        ]
+      }
     }
   ],
   "changes": [
@@ -320,6 +395,60 @@
           ]
         }
       },
+      {
+        "name": "edit",
+        "description": "Edit an existing UTF-8 text file by replacing literal text.",
+        "parameters": {
+          "type": "object",
+          "properties": {
+            "file_path": {
+              "type": "string",
+              "description": "Path to edit, resolved by the filesystem backend."
+            },
+            "old_string": {
+              "type": "string",
+              "description": "Literal text to replace. Must match exactly."
+            },
+            "new_string": {
+              "type": "string",
+              "description": "Literal replacement text. Use an empty string to delete the match."
+            },
+            "replace_all": {
+              "type": "boolean",
+              "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+            }
+          },
+          "required": [
+            "file_path",
+            "old_string",
+            "new_string"
+          ]
+        }
+      },
+      {
+        "name": "read",
+        "description": "Read a UTF-8 text file and return line-numbered content.",
+        "parameters": {
+          "type": "object",
+          "properties": {
+            "file_path": {
+              "type": "string",
+              "description": "Path to read, resolved by the filesystem backend."
+            },
+            "offset": {
+              "type": "number",
+              "description": "1-based first line to return. Defaults to 1."
+            },
+            "limit": {
+              "type": "number",
+              "description": "Maximum number of lines to return. Defaults to 2000."
+            }
+          },
+          "required": [
+            "file_path"
+          ]
+        }
+      },
       {
         "name": "skill",
         "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
@@ -546,6 +675,27 @@
             "meta"
           ]
         }
+      },
+      {
+        "name": "write",
+        "description": "Create or fully replace a UTF-8 text file.",
+        "parameters": {
+          "type": "object",
+          "properties": {
+            "file_path": {
+              "type": "string",
+              "description": "Path to write, resolved by the filesystem backend."
+            },
+            "content": {
+              "type": "string",
+              "description": "Full UTF-8 text content to write."
+            }
+          },
+          "required": [
+            "file_path",
+            "content"
+          ]
+        }
       }
     ]
   ]

+ 6 - 0
examples/acp-agent/tests/snapshots/skill-load/system-prompt.golden.md

@@ -5,6 +5,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.

+ 75 - 0
examples/acp-agent/tests/snapshots/skill-load/tool-schemas.golden.json

@@ -45,6 +45,60 @@
         ]
       }
     },
+    {
+      "name": "edit",
+      "description": "Edit an existing UTF-8 text file by replacing literal text.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to edit, resolved by the filesystem backend."
+          },
+          "old_string": {
+            "type": "string",
+            "description": "Literal text to replace. Must match exactly."
+          },
+          "new_string": {
+            "type": "string",
+            "description": "Literal replacement text. Use an empty string to delete the match."
+          },
+          "replace_all": {
+            "type": "boolean",
+            "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+          }
+        },
+        "required": [
+          "file_path",
+          "old_string",
+          "new_string"
+        ]
+      }
+    },
+    {
+      "name": "read",
+      "description": "Read a UTF-8 text file and return line-numbered content.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to read, resolved by the filesystem backend."
+          },
+          "offset": {
+            "type": "number",
+            "description": "1-based first line to return. Defaults to 1."
+          },
+          "limit": {
+            "type": "number",
+            "description": "Maximum number of lines to return. Defaults to 2000."
+          }
+        },
+        "required": [
+          "file_path"
+        ]
+      }
+    },
     {
       "name": "skill",
       "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
@@ -271,6 +325,27 @@
           "meta"
         ]
       }
+    },
+    {
+      "name": "write",
+      "description": "Create or fully replace a UTF-8 text file.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to write, resolved by the filesystem backend."
+          },
+          "content": {
+            "type": "string",
+            "description": "Full UTF-8 text content to write."
+          }
+        },
+        "required": [
+          "file_path",
+          "content"
+        ]
+      }
     }
   ],
   "changes": []

+ 6 - 0
examples/acp-agent/tests/snapshots/text-turn/system-prompt.golden.md

@@ -5,6 +5,12 @@ You are a coding assistant powered by the deepseek-v4-flash model. Your working
 Verify your work by running the code or tests. Keep answers brief and factual.
 
 
+Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files.
+
+Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes.
+
+Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session.
+
 Check the [exit code: N] marker on every bash result; investigate failures before moving on.
 
 Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering.

+ 75 - 0
examples/acp-agent/tests/snapshots/text-turn/tool-schemas.golden.json

@@ -45,6 +45,60 @@
         ]
       }
     },
+    {
+      "name": "edit",
+      "description": "Edit an existing UTF-8 text file by replacing literal text.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to edit, resolved by the filesystem backend."
+          },
+          "old_string": {
+            "type": "string",
+            "description": "Literal text to replace. Must match exactly."
+          },
+          "new_string": {
+            "type": "string",
+            "description": "Literal replacement text. Use an empty string to delete the match."
+          },
+          "replace_all": {
+            "type": "boolean",
+            "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once."
+          }
+        },
+        "required": [
+          "file_path",
+          "old_string",
+          "new_string"
+        ]
+      }
+    },
+    {
+      "name": "read",
+      "description": "Read a UTF-8 text file and return line-numbered content.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to read, resolved by the filesystem backend."
+          },
+          "offset": {
+            "type": "number",
+            "description": "1-based first line to return. Defaults to 1."
+          },
+          "limit": {
+            "type": "number",
+            "description": "Maximum number of lines to return. Defaults to 2000."
+          }
+        },
+        "required": [
+          "file_path"
+        ]
+      }
+    },
     {
       "name": "skill",
       "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.",
@@ -271,6 +325,27 @@
           "meta"
         ]
       }
+    },
+    {
+      "name": "write",
+      "description": "Create or fully replace a UTF-8 text file.",
+      "parameters": {
+        "type": "object",
+        "properties": {
+          "file_path": {
+            "type": "string",
+            "description": "Path to write, resolved by the filesystem backend."
+          },
+          "content": {
+            "type": "string",
+            "description": "Full UTF-8 text content to write."
+          }
+        },
+        "required": [
+          "file_path",
+          "content"
+        ]
+      }
     }
   ],
   "changes": []

+ 0 - 8
examples/acp-agent/workspace-context.cordis.snapshot.yml

@@ -25,13 +25,5 @@
 
             Verify your work by running the code or tests. Keep answers brief and factual.
       - insert:
-          - id: fs-local
-            name: '@deepseek-ai/dsh-fs-local'
-            config:
-              cwd: !!js process.cwd()
-          - id: fs-policy
-            name: '@deepseek-ai/dsh-fs-policy'
-          - id: tool-fs
-            name: '@deepseek-ai/dsh-tool-fs'
           - id: llm-replay
             name: '@deepseek-ai/dsh-llm-replay'

+ 0 - 9
examples/acp-agent/workspace-context.cordis.yml

@@ -21,12 +21,3 @@
             You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
 
             Verify your work by running the code or tests. Keep answers brief and factual.
-      - insert:
-          - id: fs-local
-            name: '@deepseek-ai/dsh-fs-local'
-            config:
-              cwd: !!js process.cwd()
-          - id: fs-policy
-            name: '@deepseek-ai/dsh-fs-policy'
-          - id: tool-fs
-            name: '@deepseek-ai/dsh-tool-fs'

+ 2 - 1
packages/examples/acp-demo/README.md

@@ -32,12 +32,13 @@ Because the package wires no logger entry, an ACP leaf has **nothing to get wron
 | `toolOrder` | — | explicit model-facing tool order (a name list with one `'<unlisted-tools>'` rest entry; absent — lexicographic; an unregistered name fails each turn at prompt assembly), routed to `dsh-system-prompt` |
 | `dshHome` | `$DSH_HOME` or `~/.dsh` | Harness home exposed to model bash and used by local skill discovery |
 | `tools` | `{ mode: 'native' }` | tool-registry presentation config (`native` / `code` / `both`), routed through `dsh-agent-spine-demo` |
+| `workspaceContext` | (required) | workspace-instruction byte budget/config, or `false`; routed to the providerless-safe `dsh-workspace-context` plugin |
 | `skills` | owner defaults | registry-cache, local-provider, and model-facing skill-tool config, routed through `dsh-agent-spine-demo` |
 | `toolBash` | owner defaults | model-facing bash config routed through `dsh-agent-spine-demo`, including bash's producer-local `enableRunInBackground` |
 | `toolTasks` | owner defaults | generic `task_output` wait bounds routed through `dsh-agent-spine-demo` |
 | `persistenceRoot` | `./.sessions` | the JSONL backend's root directory |
 
-The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the real model, `llm-replay` for keyless snapshot replay) and a bash executor.
+The leaf supplies the swappable backends: an LLM adapter (`llm-deepseek` for the real model, `llm-replay` for keyless snapshot replay), a bash executor, and optionally a `ctx.fs` provider. Workspace context becomes a no-op without `ctx.fs`; the shipped [`examples/acp-agent/cordis.yml`](../../../examples/acp-agent/cordis.yml) selects `dsh-fs-local`, `dsh-fs-policy`, and `dsh-tool-fs` so baseline instructions and model-facing `read`/`write`/`edit` share one filesystem suite.
 
 ## The bin