diff --git a/examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml b/examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml
index a741091cce..8dad8832a0 100644
--- a/examples/acp-agent/code-mode-workspace-context.cordis.snapshot.yml
+++ b/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
diff --git a/examples/acp-agent/code-mode-workspace-context.cordis.yml b/examples/acp-agent/code-mode-workspace-context.cordis.yml
index b932f06e64..71edf9750e 100644
--- a/examples/acp-agent/code-mode-workspace-context.cordis.yml
+++ b/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'
diff --git a/examples/acp-agent/composition.md b/examples/acp-agent/composition.md
index 194ff3dee0..b3604caa84 100644
--- a/examples/acp-agent/composition.md
+++ b/examples/acp-agent/composition.md
@@ -18,6 +18,12 @@ flowchart LR
cfg --> plugin_acp_approval
plugin_acp_permission["permission
@deepseek-ai/dsh-permission"]
cfg --> plugin_acp_permission
+ plugin_acp_fs_local["fs-local
@deepseek-ai/dsh-fs-local"]
+ cfg --> plugin_acp_fs_local
+ plugin_acp_fs_policy["fs-policy
@deepseek-ai/dsh-fs-policy"]
+ cfg --> plugin_acp_fs_policy
+ plugin_acp_tool_fs["tool-fs
@deepseek-ai/dsh-tool-fs"]
+ cfg --> plugin_acp_tool_fs
plugin_acp_acp_agent["acp-agent
@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` |
diff --git a/examples/acp-agent/cordis.yml b/examples/acp-agent/cordis.yml
index cb8890dae7..d4d7b90554 100644
--- a/examples/acp-agent/cordis.yml
+++ b/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.
diff --git a/examples/acp-agent/fs.cordis.snapshot.yml b/examples/acp-agent/fs.cordis.snapshot.yml
index da0b2ca59b..a551251564 100644
--- a/examples/acp-agent/fs.cordis.snapshot.yml
+++ b/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:
diff --git a/examples/acp-agent/fs.cordis.yml b/examples/acp-agent/fs.cordis.yml
index 52ca959a89..882384f319 100644
--- a/examples/acp-agent/fs.cordis.yml
+++ b/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:
diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md b/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md
index b8acef973c..257aa9a0ab 100644
--- a/examples/acp-agent/tests/snapshots/advanced-toolchain/system-prompt.golden.md
+++ b/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;
+ /** 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;
+ /** 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;
/** 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;
}): Promise;
+ /** 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;
}
```
diff --git a/examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.golden.json b/examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.golden.json
index 978819fa1f..2e0f5efdf6 100644
--- a/examples/acp-agent/tests/snapshots/advanced-toolchain/tool-schemas.golden.json
+++ b/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": []
diff --git a/examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md b/examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md
index f1a3b9ff92..cc8e2d1301 100644
--- a/examples/acp-agent/tests/snapshots/both-mode-turn/system-prompt.golden.md
+++ b/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;
+ /** 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;
+ /** 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;
/** 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;
}): Promise;
+ /** 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;
}
```
diff --git a/examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.golden.json b/examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.golden.json
index edf1a7c001..068a1d80e0 100644
--- a/examples/acp-agent/tests/snapshots/both-mode-turn/tool-schemas.golden.json
+++ b/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": []
diff --git a/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md b/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md
index f1a3b9ff92..cc8e2d1301 100644
--- a/examples/acp-agent/tests/snapshots/code-mode-turn/system-prompt.golden.md
+++ b/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;
+ /** 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;
+ /** 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;
/** 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;
}): Promise;
+ /** 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;
}
```
diff --git a/examples/acp-agent/tests/snapshots/model-switching/system-prompt.golden.md b/examples/acp-agent/tests/snapshots/model-switching/system-prompt.golden.md
index b9701e538c..e89336a2fe 100644
--- a/examples/acp-agent/tests/snapshots/model-switching/system-prompt.golden.md
+++ b/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.
diff --git a/examples/acp-agent/tests/snapshots/model-switching/tool-schemas.golden.json b/examples/acp-agent/tests/snapshots/model-switching/tool-schemas.golden.json
index 7c814257fb..7ccd09642b 100644
--- a/examples/acp-agent/tests/snapshots/model-switching/tool-schemas.golden.json
+++ b/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"
+ ]
+ }
}
]
]
diff --git a/examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md b/examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md
index 622bc4e23a..47a68e9a03 100644
--- a/examples/acp-agent/tests/snapshots/permission-switching/system-prompt.golden.md
+++ b/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.
diff --git a/examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.golden.json b/examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.golden.json
index 7c814257fb..7ccd09642b 100644
--- a/examples/acp-agent/tests/snapshots/permission-switching/tool-schemas.golden.json
+++ b/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"
+ ]
+ }
}
]
]
diff --git a/examples/acp-agent/tests/snapshots/skill-load/system-prompt.golden.md b/examples/acp-agent/tests/snapshots/skill-load/system-prompt.golden.md
index 43ecb9746f..ddf502a773 100644
--- a/examples/acp-agent/tests/snapshots/skill-load/system-prompt.golden.md
+++ b/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.
diff --git a/examples/acp-agent/tests/snapshots/skill-load/tool-schemas.golden.json b/examples/acp-agent/tests/snapshots/skill-load/tool-schemas.golden.json
index e422a063da..4b08a1e365 100644
--- a/examples/acp-agent/tests/snapshots/skill-load/tool-schemas.golden.json
+++ b/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": []
diff --git a/examples/acp-agent/tests/snapshots/text-turn/system-prompt.golden.md b/examples/acp-agent/tests/snapshots/text-turn/system-prompt.golden.md
index 43ecb9746f..ddf502a773 100644
--- a/examples/acp-agent/tests/snapshots/text-turn/system-prompt.golden.md
+++ b/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.
diff --git a/examples/acp-agent/tests/snapshots/text-turn/tool-schemas.golden.json b/examples/acp-agent/tests/snapshots/text-turn/tool-schemas.golden.json
index e422a063da..4b08a1e365 100644
--- a/examples/acp-agent/tests/snapshots/text-turn/tool-schemas.golden.json
+++ b/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": []
diff --git a/examples/acp-agent/workspace-context.cordis.snapshot.yml b/examples/acp-agent/workspace-context.cordis.snapshot.yml
index e0b02cbb21..c5cccac519 100644
--- a/examples/acp-agent/workspace-context.cordis.snapshot.yml
+++ b/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'
diff --git a/examples/acp-agent/workspace-context.cordis.yml b/examples/acp-agent/workspace-context.cordis.yml
index 1b8c0279f0..9f422f65b9 100644
--- a/examples/acp-agent/workspace-context.cordis.yml
+++ b/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'
diff --git a/packages/examples/acp-demo/README.md b/packages/examples/acp-demo/README.md
index 1dbda9a08a..2fb444b10e 100644
--- a/packages/examples/acp-demo/README.md
+++ b/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 `''` 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