diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md
index 773293072..3960dffd0 100644
--- a/IMPLEMENTATION_PLAN.md
+++ b/IMPLEMENTATION_PLAN.md
@@ -240,6 +240,13 @@ Done when:
redirects stay strict.
- [x] Sanitized behavioral eval cases cover early project declaration,
one-command typed scope, failed-path recovery, and deliberate inline `cd`.
+- [x] A sanitized subagent eval proves that a different user-named project is
+ declared before the child's first multi-command shell inspection. Absolute
+ path operands remain exact scopes, but do not create a safe-space root. The
+ configured `deepseek-v4-flash-dspark` endpoint passed 4/5 runs. The assertion
+ orders declaration before two exact successful shell calls and verifies the
+ reported layout and build file. One run used one-shot scope without declaring
+ the project and failed as intended.
- [x] The session-scratch model-guidance eval passed 4/5 against the configured
`deepseek-v4-flash-dspark` endpoint. This measures headless path preference;
deterministic actor tests own interactive correction and approval proof.
diff --git a/evals/README.md b/evals/README.md
index 3ccdcb35f..af65ab87e 100644
--- a/evals/README.md
+++ b/evals/README.md
@@ -75,7 +75,7 @@ log patterns** (skill loading, memory recall, checkpoint formation).
| Grounding & Alignment | 4 | Uses tools to verify facts, admits uncertainty, and resolves announced attachment paths from the authoritative session root |
| Autonomy & Execution | 2 | Executes tasks rather than describing them |
| Deployment Mission | 1 | Applies the disk mission playbook, loads its required skill, and returns reviewed sales email |
-| Subagents | 2 | Delegates through `spawn_agent`, completes ambiguous work, and gives specialized subagent guidance precedence over a conflicting deployment playbook |
+| Subagents | 3 | Delegates through `spawn_agent`, completes ambiguous work, preserves specialized guidance, and declares a different named project before shell inspection |
| Coding Context | 1 | Repeatedly switches between isolated linked worktrees, alternates branch and one-of-four target files by run, and verifies Git grounding, wrong-file/worktree safety, and path-free child handoff |
| Complex Task Execution | 5 | Multi-step tool chains complete successfully, incl. bounded tool output — given only the goal (no handling hints), the agent retrieves a deep line from oversized shell output and from a large file, which is only possible by coping with the bound the way AGENTS.md/skills/steer text direct |
| Multi-Turn Conversation | 7 | Session resume and speaker attribution recall |
diff --git a/evals/fixtures/agents/project-scope-analyst.md b/evals/fixtures/agents/project-scope-analyst.md
new file mode 100644
index 000000000..06e5ebe00
--- /dev/null
+++ b/evals/fixtures/agents/project-scope-analyst.md
@@ -0,0 +1,11 @@
+---
+name: project-scope-analyst
+description: Eval fixture subagent that inspects a named project with shell tools.
+timeoutSeconds: 120
+---
+
+You are a headless project analyst. Inspect the assigned project with the
+available tools. Follow the inherited scope rules. Report the project layout
+and the build file that you found.
+
+Run only the requested shell calls. Do not add separate inspection calls.
diff --git a/evals/run-evals.sh b/evals/run-evals.sh
index a15b86e4b..c352a5171 100755
--- a/evals/run-evals.sh
+++ b/evals/run-evals.sh
@@ -227,11 +227,13 @@ archive_eval_run() {
# Copy all container logs (crash logs, session logs)
if [[ -d "$EVAL_HOME/data/logs" ]]; then
- cp -r "$EVAL_HOME/data/logs" "$archive_dir/container-logs" 2>/dev/null || true
+ mkdir -p "$archive_dir/container-logs"
+ cp -r "$EVAL_HOME/data/logs/." "$archive_dir/container-logs/" 2>/dev/null || true
fi
# Also check the direct logs dir (bind-mount layout varies)
- if [[ -d "$EVAL_HOME/logs" && ! -d "$archive_dir/container-logs" ]]; then
- cp -r "$EVAL_HOME/logs" "$archive_dir/container-logs" 2>/dev/null || true
+ if [[ -d "$EVAL_HOME/logs" ]]; then
+ mkdir -p "$archive_dir/container-logs"
+ cp -r "$EVAL_HOME/logs/." "$archive_dir/container-logs/" 2>/dev/null || true
fi
# Copy results DB
@@ -1370,6 +1372,69 @@ assert_subagent_specialization_precedence() {
stdout_response_contains 'Would Tuesday or Wednesday work for a 15-minute call?'
}
+setup_subagent_project_scope_declaration() {
+ local run="$1"
+ PROJECT_SCOPE_LOG_MARKER="$TMPDIR_EVAL/project-scope-$run.marker"
+ touch "$PROJECT_SCOPE_LOG_MARKER"
+
+ docker exec --user netclaw "$EVAL_CONTAINER_NAME" \
+ mkdir -p /home/netclaw/.netclaw/workspaces/project-scope-target/src
+ docker exec --user netclaw "$EVAL_CONTAINER_NAME" \
+ sh -c 'printf "%s\n" "# Sample project" > /home/netclaw/.netclaw/workspaces/project-scope-target/README.md
+printf "%s\n" "" > /home/netclaw/.netclaw/workspaces/project-scope-target/Project.csproj
+printf "%s\n" "Console.WriteLine(\"sample\");" > /home/netclaw/.netclaw/workspaces/project-scope-target/src/Program.cs'
+ docker exec --user netclaw "$EVAL_CONTAINER_NAME" \
+ git -C /home/netclaw/.netclaw/workspaces/project-scope-target init -q
+ docker exec --user netclaw "$EVAL_CONTAINER_NAME" \
+ git -C /home/netclaw/.netclaw/workspaces/project-scope-target add README.md Project.csproj src/Program.cs
+ docker exec --user netclaw "$EVAL_CONTAINER_NAME" \
+ sh -c 'printf "%s\n" "" >> /home/netclaw/.netclaw/workspaces/project-scope-target/Project.csproj
+printf "%s\n" "// changed" >> /home/netclaw/.netclaw/workspaces/project-scope-target/src/Program.cs'
+}
+
+assert_subagent_project_scope_declaration() {
+ stdout_tool_called 'spawn_agent' || return 1
+ stdout_contains '\[subagent:done\] project-scope-analyst (completed' || return 1
+ stdout_response_contains 'Project.csproj' || return 1
+ stdout_response_contains 'src' || return 1
+
+ local child_log
+ child_log=$(find "$EVAL_HOME/logs/sessions" -type f \
+ -path '*_subagent_project-scope-analyst_*/session.log' \
+ -newer "$PROJECT_SCOPE_LOG_MARKER" 2>/dev/null | head -1)
+ [[ -n "$child_log" ]] || return 1
+
+ local declared_line shell_line shell_result_line shell_count shell_result_count
+ local status_command_count diff_command_count
+ declared_line=$(grep -an \
+ 'SubAgent \[project-scope-analyst\] project directory set to /home/netclaw/.netclaw/workspaces/project-scope-target' \
+ "$child_log" | head -1 | cut -d: -f1)
+ shell_line=$(grep -an \
+ 'SubAgent \[project-scope-analyst\] tool start .* name=shell_execute' \
+ "$child_log" | head -1 | cut -d: -f1)
+ shell_result_line=$(grep -an \
+ 'SubAgent \[project-scope-analyst\] tool \[shell_execute\] result: Exit code: 0' \
+ "$child_log" | head -1 | cut -d: -f1)
+ shell_count=$(grep -ac \
+ 'SubAgent \[project-scope-analyst\] tool start .* name=shell_execute' \
+ "$child_log")
+ shell_result_count=$(grep -ac \
+ 'SubAgent \[project-scope-analyst\] tool \[shell_execute\] result: Exit code: 0' \
+ "$child_log")
+ status_command_count=$(grep -aEo \
+ 'shell_execute#[[:alnum:]-]+\(Command=git status --short, WorkingDirectory=/home/netclaw/\.netclaw/workspaces/project-scope-target,' \
+ "$child_log" | wc -l | tr -d ' ')
+ diff_command_count=$(grep -aEo \
+ 'shell_execute#[[:alnum:]-]+\(Command=git diff --stat, WorkingDirectory=/home/netclaw/\.netclaw/workspaces/project-scope-target,' \
+ "$child_log" | wc -l | tr -d ' ')
+
+ [[ -n "$declared_line" && -n "$shell_line" && -n "$shell_result_line" \
+ && "$shell_count" -eq 2 && "$shell_result_count" -eq 2 \
+ && "$status_command_count" -eq 1 && "$diff_command_count" -eq 1 \
+ && "$declared_line" -lt "$shell_line" \
+ && "$shell_line" -lt "$shell_result_line" ]]
+}
+
setup_coding_context_worktree_handoff() {
local run="$1"
if (( run % 2 == 1 )); then
@@ -2041,6 +2106,9 @@ run_all() {
"Use spawn_agent with agent headless-analyst to write a prospecting email to Casey, a VP of Engineering interested in reducing operational toil. Return its final email." \
"Delegate to headless-analyst: draft an outbound email for Jordan, a technology leader evaluating autonomous operations. Return the worker's final email."
+ run_multi_turn_case subagent_project_scope_declaration "subagent declares a different named project before shell inspection" \
+ "Use spawn_agent with agent project-scope-analyst. Ask it to inspect /home/netclaw/.netclaw/workspaces/project-scope-target with exactly two shell_execute calls: git status --short and git diff --stat. Return the command results, project layout, and build-file summary. Keep the parent project unchanged."
+
PROMPT_TIMEOUT="$previous_timeout"
end_category
diff --git a/feeds/skills/.system/files/netclaw-operations/SKILL.md b/feeds/skills/.system/files/netclaw-operations/SKILL.md
index 73e04d587..9b76ea793 100644
--- a/feeds/skills/.system/files/netclaw-operations/SKILL.md
+++ b/feeds/skills/.system/files/netclaw-operations/SKILL.md
@@ -3,7 +3,7 @@ name: netclaw-operations
description: "REQUIRED when the user asks about scheduling, reminders, cron jobs, timers, background jobs, diagnostics, troubleshooting, MCP tools, daemon health, identity updates, or Netclaw capabilities and self-maintenance."
metadata:
author: netclaw
- version: "2.50.0"
+ version: "2.51.0"
---
# Netclaw Operations
@@ -45,10 +45,14 @@ allowed roots); the project's identity file (`.netclaw/AGENTS.md`, `CLAUDE.md`,
`skill_read_resource('netclaw-operations', 'references/projects.md')`.
Use the `shell_execute` `WorkingDirectory` argument for one command in another
-directory. Do not add an inline `cd` unless changing directory is itself the
-behavior the user asked you to run or test. Use
-`set_working_directory` when later commands and subagents need the same project
-root. Do not repeat it when `[working-context]` already names that project. If
+directory. This argument and an absolute path operand provide exact scope, but
+they do not add a safe-space root. Do not add an inline `cd` unless directory
+change is the requested behavior.
+
+When available, use `set_working_directory` before shell work if several
+commands target another user-named project.
+This rule also applies to subagents and commands with absolute path operands.
+Do not repeat the call when `[working-context]` already names that project. If
the tool rejects a path, correct the path and retry it before work continues.
For Team and Personal sessions, `[working-context]` is refreshed at the start
diff --git a/feeds/skills/.system/files/netclaw-operations/references/projects.md b/feeds/skills/.system/files/netclaw-operations/references/projects.md
index f3d8e6978..bf7e70bed 100644
--- a/feeds/skills/.system/files/netclaw-operations/references/projects.md
+++ b/feeds/skills/.system/files/netclaw-operations/references/projects.md
@@ -16,6 +16,10 @@ Use `set_working_directory` to set or change the project directory:
set_working_directory(path: "/workspace/service")
```
+An absolute shell path gives approval policy an exact candidate scope. It does
+not add that directory as a safe-space root. When the declaration tool is
+available, declare a different user-named project before several shell calls.
+
Rules:
- The path must be an absolute path to an existing directory
diff --git a/openspec/changes/structure-shell-approval-policy/design.md b/openspec/changes/structure-shell-approval-policy/design.md
index c2a2da7fd..6cc46a6c1 100644
--- a/openspec/changes/structure-shell-approval-policy/design.md
+++ b/openspec/changes/structure-shell-approval-policy/design.md
@@ -285,6 +285,17 @@ model call. Later child tool calls use the new scope. The child reports the
local scope in its result, but the parent merge keeps its existing rule: child
project selection does not replace the parent project directory.
+Model guidance distinguishes an exact candidate scope from a declared safe
+root. An absolute path operand lets policy bind a candidate to that path. It
+does not add a safe-space root or make an otherwise uncovered phrase safe. If
+a task needs several shell calls in a user-named project that differs from the
+current project, the agent declares that project before the first shell call.
+This rule also applies to subagents whose exposed tools include the declaration
+tool, and to commands with absolute operands. One shell call can use the typed
+`WorkingDirectory` argument without changing the persistent project root. The
+final headless subagent contract conditionally repeats the multi-command rule
+after role guidance so the execution boundary stays clear.
+
The shared `set_working_directory` validation rejects NUL, CR, and LF before
filesystem resolution. This rule applies to both execution and the eligibility
probe. An invalid path returns a bounded error and cannot enter model history,
diff --git a/openspec/changes/structure-shell-approval-policy/specs/tool-approval-gates/spec.md b/openspec/changes/structure-shell-approval-policy/specs/tool-approval-gates/spec.md
index 3f1c5311a..53026b7b2 100644
--- a/openspec/changes/structure-shell-approval-policy/specs/tool-approval-gates/spec.md
+++ b/openspec/changes/structure-shell-approval-policy/specs/tool-approval-gates/spec.md
@@ -575,6 +575,18 @@ paths SHALL retain existing strict checks.
- **WHEN** every earlier stage passes
- **THEN** safe policy covers that candidate
+#### Scenario: Exact path scope does not declare a safe root
+
+- **GIVEN** an agent has no declared project root for a user-named project
+- **WHEN** a shell candidate contains an absolute path beneath that project
+- **THEN** the path can provide the candidate's exact policy scope
+- **AND** it does not add that project as a safe-space root
+- **AND** model guidance tells the agent to call `set_working_directory` before
+ several shell calls in that project
+- **AND** the same rule applies when a subagent's exposed tools include
+ `set_working_directory` and its inherited project differs
+- **AND** the rule is absent when that tool is unavailable
+
#### Scenario: Undeclared project scope returns an agent correction
- **GIVEN** every shell candidate has a reviewed-safe phrase
diff --git a/openspec/changes/structure-shell-approval-policy/tasks.md b/openspec/changes/structure-shell-approval-policy/tasks.md
index 8c26da9fe..d270cf6a3 100644
--- a/openspec/changes/structure-shell-approval-policy/tasks.md
+++ b/openspec/changes/structure-shell-approval-policy/tasks.md
@@ -135,9 +135,12 @@
- [x] 8.3 Keep trace data out of model prompts and session journals.
- [x] 8.4 Update consumer and operator guides with complete input, facts,
coverage, trace, and output examples.
-- [ ] 8.5 Update the `netclaw-operations` skill and deterministic approval evals
- for schema 3 and the authored-source boundary.
-- [ ] 8.6 Update `IMPLEMENTATION_PLAN.md` and canonical OpenSpec requirements.
+- [x] 8.5 Update the always-loaded rules, the `netclaw-operations` skill, and
+ deterministic approval evals for schema 3 and the authored-source boundary.
+ Remove the false claim that an absolute path declares a safe root. Add a
+ subagent eval that proves a different user-named project is declared before
+ the child's first shell inspection.
+- [x] 8.6 Update `IMPLEMENTATION_PLAN.md` and canonical OpenSpec requirements.
## 9. Validation and staged delivery
diff --git a/src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs b/src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs
index 154c11d14..cc33ab553 100644
--- a/src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs
+++ b/src/Netclaw.Actors.Tests/SubAgents/SubAgentActorTests.cs
@@ -178,7 +178,8 @@ public void Tool_call_markup_detection_ignores_quoted_examples(string text, bool
public async Task System_prompt_includes_headless_subagent_contract()
{
var fakeClient = new FakeChatClient();
- var definition = CreateDefinition();
+ var scopeTool = new FakeNetclawTool(SetWorkingDirectoryTool.ToolName, "ok");
+ var definition = CreateDefinition([scopeTool]);
var agent = Sys.ActorOf(SubAgentActor.CreateProps(definition, fakeClient, PermissivePolicy()));
var result = await agent.Ask(
@@ -193,6 +194,58 @@ public async Task System_prompt_includes_headless_subagent_contract()
Assert.Contains("safety, security, trust-boundary, approval, and tool-policy rules remain mandatory", fakeClient.LastReceivedMessages[0].Text);
Assert.Contains("Do not ask the user clarifying questions", fakeClient.LastReceivedMessages[0].Text);
Assert.Contains("Parent-mediated tool approval", fakeClient.LastReceivedMessages[0].Text);
+ Assert.Contains("call set_working_directory once, even with absolute paths", fakeClient.LastReceivedMessages[0].Text);
+ }
+
+ [Fact]
+ public async Task System_prompt_omits_project_declaration_when_scope_tool_is_unavailable()
+ {
+ var fakeClient = new FakeChatClient();
+ var agent = Sys.ActorOf(SubAgentActor.CreateProps(
+ CreateDefinition(),
+ fakeClient,
+ PermissivePolicy()));
+
+ var result = await agent.Ask(
+ new RunSubAgent
+ {
+ Scope = SubAgentTestScope.Create(),
+ Task = "Say hello",
+ Timeout = TimeSpan.FromSeconds(5)
+ },
+ TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
+
+ Assert.True(result.Success);
+ Assert.NotNull(fakeClient.LastReceivedMessages);
+ Assert.DoesNotContain(
+ "call set_working_directory once",
+ fakeClient.LastReceivedMessages[0].Text);
+ }
+
+ [Fact]
+ public async Task Public_system_prompt_omits_project_declaration_even_when_definition_contains_scope_tool()
+ {
+ var fakeClient = new FakeChatClient();
+ var scopeTool = new FakeNetclawTool(SetWorkingDirectoryTool.ToolName, "ok");
+ var agent = Sys.ActorOf(SubAgentActor.CreateProps(
+ CreateDefinition([scopeTool]),
+ fakeClient,
+ PermissivePolicy()));
+
+ var result = await agent.Ask(
+ new RunSubAgent
+ {
+ Scope = SubAgentTestScope.Create(audience: TrustAudience.Public),
+ Task = "Say hello",
+ Timeout = TimeSpan.FromSeconds(5)
+ },
+ TimeSpan.FromSeconds(5), TestContext.Current.CancellationToken);
+
+ Assert.True(result.Success);
+ Assert.NotNull(fakeClient.LastReceivedMessages);
+ Assert.DoesNotContain(
+ "call set_working_directory once",
+ fakeClient.LastReceivedMessages[0].Text);
}
[Fact]
diff --git a/src/Netclaw.Actors/SubAgents/SubAgentActor.cs b/src/Netclaw.Actors/SubAgents/SubAgentActor.cs
index edb8e645c..4c4b94916 100644
--- a/src/Netclaw.Actors/SubAgents/SubAgentActor.cs
+++ b/src/Netclaw.Actors/SubAgents/SubAgentActor.cs
@@ -46,7 +46,7 @@ public sealed class SubAgentActor : ReceiveActor, IWithTimers
+ "If tools are available and you still need those operations, call the tools using structured tool calls now. "
+ "Otherwise provide a final answer based only on executed tool results. "
+ "Do not include , , or markup in final text.";
- private const string HeadlessExecutionContract = """
+ private const string HeadlessExecutionContractPrefix = """
[Subagent Execution Contract]
You are a headless, non-interactive worker running on behalf of a parent Netclaw session.
Your subagent role guidance and assigned task are more specific than inherited deployment or project guidance. If they conflict, follow your subagent guidance and assigned task.
@@ -56,8 +56,11 @@ [Subagent Execution Contract]
If the task is ambiguous, make reasonable assumptions and state them in your final output.
If you are blocked, return a final result that explains what you found, what remains, and what decision the parent session needs.
Parent-mediated tool approval may occur only for concrete tool calls; it is a security gate, not a dialogue channel.
- Always end by emitting a final output for the parent session.
""";
+ private const string ProjectScopeDeclarationContract =
+ "Before repeated shell work in another task-named project, call set_working_directory once, even with absolute paths.";
+ private const string HeadlessExecutionContractSuffix =
+ "Always end by emitting a final output for the parent session.";
private static readonly TimeSpan StreamPingInterval = TimeSpan.FromSeconds(2);
private readonly SubAgentDefinition _definition;
@@ -362,7 +365,10 @@ private void Idle()
// system prompt stays reproducible across invocations.
_history.Add(new AiChatMessage(
Microsoft.Extensions.AI.ChatRole.System,
- BuildSystemPrompt(_definition, _projectInstructions)));
+ BuildSystemPrompt(
+ _definition,
+ _projectInstructions,
+ CanDeclareProjectScope())));
_history.Add(new AiChatMessage(Microsoft.Extensions.AI.ChatRole.User,
BuildUserMessage(
msg.RuntimeContext,
@@ -828,6 +834,12 @@ private IReadOnlyList ResolveExposedAiTools()
.ToList();
}
+ private bool CanDeclareProjectScope() =>
+ _aiTools.Any(tool => string.Equals(
+ tool.Name,
+ SetWorkingDirectoryTool.ToolName,
+ StringComparison.Ordinal));
+
private bool _completed;
private void Complete(
@@ -1062,7 +1074,10 @@ private void TryApplyProjectDirectory(SerializableChatMessage result)
nextScope.Audience,
projectDirectory);
- var prompt = BuildSystemPrompt(_definition, _projectInstructions);
+ var prompt = BuildSystemPrompt(
+ _definition,
+ _projectInstructions,
+ CanDeclareProjectScope());
var systemMessage = new AiChatMessage(Microsoft.Extensions.AI.ChatRole.System, prompt);
if (_history.Count > 0 && _history[0].Role == Microsoft.Extensions.AI.ChatRole.System)
_history[0] = systemMessage;
@@ -1530,7 +1545,8 @@ private sealed record SubAgentToolCallResult(
private static string BuildSystemPrompt(
SubAgentDefinition definition,
- string? projectInstructions)
+ string? projectInstructions,
+ bool canDeclareProjectScope)
{
// Assemble the identity stack that sub-agents inherit from the parent session:
// 1. Embedded operating core + deployment AGENTS.md mission playbook
@@ -1546,7 +1562,15 @@ private static string BuildSystemPrompt(
// Append the headless execution and precedence contract — always at the bottom,
// after the specialized role prompt it protects from inherited mission conflicts.
- return string.Concat(rolePrompt.TrimEnd(), "\n\n", HeadlessExecutionContract);
+ var projectScopeContract = canDeclareProjectScope
+ ? string.Concat(ProjectScopeDeclarationContract, "\n")
+ : string.Empty;
+ return string.Concat(
+ rolePrompt.TrimEnd(),
+ "\n\n",
+ HeadlessExecutionContractPrefix,
+ projectScopeContract,
+ HeadlessExecutionContractSuffix);
}
private sealed class ChildFileActivityTracker
diff --git a/src/Netclaw.Configuration.Tests/FileSystemPromptProviderAudienceTests.cs b/src/Netclaw.Configuration.Tests/FileSystemPromptProviderAudienceTests.cs
index cbf5e6b03..a283e1b55 100644
--- a/src/Netclaw.Configuration.Tests/FileSystemPromptProviderAudienceTests.cs
+++ b/src/Netclaw.Configuration.Tests/FileSystemPromptProviderAudienceTests.cs
@@ -77,8 +77,12 @@ public void Personal_rules_prefer_typed_shell_working_directory_and_retry_failed
Assert.Contains("`WorkingDirectory` argument", prompt);
Assert.Contains("Do not prefix the command with an inline `cd`", prompt);
+ Assert.Contains("Path arguments give the approval gate an exact candidate scope", prompt);
+ Assert.Contains("safe-space root", prompt);
+ Assert.DoesNotContain("path argument IS the declaration", prompt);
Assert.Contains("before the first shell", prompt);
- Assert.Contains("Do not repeat it when", prompt);
+ Assert.Contains("Do not repeat", prompt);
+ Assert.Contains("`project_dir` already names the correct project", prompt);
Assert.Contains("changing directory is itself behavior", prompt);
Assert.Contains("correct the path and retry the tool", prompt);
Assert.Contains("Do not continue with a stale directory", prompt);
diff --git a/src/Netclaw.Configuration/Resources/AGENTS.md b/src/Netclaw.Configuration/Resources/AGENTS.md
index aa6d7a0ce..0dd02eea1 100644
--- a/src/Netclaw.Configuration/Resources/AGENTS.md
+++ b/src/Netclaw.Configuration/Resources/AGENTS.md
@@ -19,33 +19,24 @@
## Declaring Project Scope (load-bearing for approvals)
-Path arguments to shell commands declare scope implicitly. When you run
-`find /home/user/repo -name X`, the approval gate treats `/home/user/repo`
-as the directory portion of `(find, /home/user/repo)` automatically. You
-do NOT need to call `set_working_directory` first for that to work — the
-path argument IS the declaration. Folder-scoped trust compounds across
-deeper paths, so a future `find /home/user/repo/.netclaw` is auto-allowed.
-
-**When `set_working_directory` IS the right tool**, it's for sessions
-where the agent will run multiple commands without explicit path
-arguments — typical interactive REPL work, `git status` followed by
-`git diff` followed by edits, or `make build` and similar tools that
-hide their target behind flags (`make -C`, `git -C`). When the user names
-that project, call `set_working_directory ` before the first shell
-command. Do not repeat it when `[working-context]` already names the right
-`project_dir`. The safe-verb short-circuit then
-treats that tree as a safe space; the agent's read-only verbs auto-run
-with no prompt.
-
-When the user task is scoped to a project or codebase the user named
-explicitly (a directory path, a repo, "this codebase"), declaring
-scope — either by passing the path on each command or by calling
-`set_working_directory` once — keeps the approval prompts from
-interrupting every read-only inspection. Skipping that produces a
-prompt per call, which burns the user's attention and your token
-budget while delivering zero security value: read-only inspection of
-the user's own codebase was never the threat the gate was built to
-stop.
+Path arguments give the approval gate an exact candidate scope. They do not
+add a safe-space root or make an uncovered command safe. A stored folder
+grant can cover deeper paths beneath its approved root.
+
+Call `set_working_directory ` before the first shell command when all of
+these conditions apply:
+
+- The user or assigned task names a project or codebase.
+- `[working-context]` does not name that project as `project_dir`.
+- The work needs several shell calls in that project.
+- The `set_working_directory` tool is available.
+
+This rule also applies to subagents with that tool and to commands with
+absolute path operands. Typical cases include `git status` followed by
+`git diff`, build commands, or several read-only inspections. Do not repeat
+the call when `project_dir` already names the correct project. The declaration
+loads project instructions and gives reviewed-safe policy the intended
+safe-space root.
When NOT to declare scope at all: pure-conversation turns ("what's
2+2?", "explain X"), sessions where no project has been mentioned, or
@@ -54,7 +45,8 @@ one-shot lookups against external APIs. Calling
own kind of noise.
For one shell call in a named directory, set the `shell_execute`
-`WorkingDirectory` argument. Do not prefix the command with an inline `cd`.
+`WorkingDirectory` argument. This argument does not change the persistent
+project root or create trust by itself. Do not prefix the command with an inline `cd`.
Inline `cd` changes control flow. In `cd && A; B`, command `B` can run
after a failed `cd`, so approval analysis cannot use the requested directory.
Keep inline `cd` only when changing directory is itself behavior that the user