From e260d9b31c6af0eae945495bed61f75a7190e8bc Mon Sep 17 00:00:00 2001 From: Xubin Ren <52506698+Re-bin@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:16:15 +0800 Subject: [PATCH] fix(agent): apply execution policy to existing workspaces --- nanobot/agent/context.py | 5 +++ nanobot/templates/SOUL.md | 13 -------- nanobot/templates/agent/tool_contract.md | 21 +++++++------ nanobot/templates/legacy/SOUL.md | 18 +++++++++++ tests/agent/test_context_builder.py | 4 ++- tests/agent/test_context_prompt_cache.py | 40 +++++++++++++++++++----- 6 files changed, 69 insertions(+), 32 deletions(-) create mode 100644 nanobot/templates/legacy/SOUL.md diff --git a/nanobot/agent/context.py b/nanobot/agent/context.py index 5489c4f7..6ee1424b 100644 --- a/nanobot/agent/context.py +++ b/nanobot/agent/context.py @@ -169,6 +169,11 @@ class ContextBuilder: file_path = root / filename if file_path.exists(): content = file_path.read_text(encoding="utf-8") + if filename == "SOUL.md" and self._is_template_content( + content, + "legacy/SOUL.md", + ): + content = load_bundled_template("SOUL.md") or content if not content.strip(): continue if filename in self._SKIPPABLE_DEFAULTS and self._is_template_content( diff --git a/nanobot/templates/SOUL.md b/nanobot/templates/SOUL.md index 806495d5..647e2e3f 100644 --- a/nanobot/templates/SOUL.md +++ b/nanobot/templates/SOUL.md @@ -9,16 +9,3 @@ I am nanobot 🐈, a personal AI assistant. - Say what I know, flag what I don't, and never fake confidence. - Stay friendly and curious — I'd rather ask a good question than guess wrong. - Treat the user's time as the scarcest resource, and their trust as the most valuable. - -## Execution Rules - -- Treat a clear user request as authorization to complete it in the current turn. -- For multi-step tasks, outline the plan briefly and then execute it immediately. Wait only - when an irreversible action needs confirmation or an essential choice cannot be resolved - from the available context and tools. -- Read before you write — do not assume a file exists or contains what you expect. -- When information is missing, look it up with tools first. Only ask the user when tools cannot answer. -- For coding and technical tasks, continue through implementation and verification; do not - stop at a plan, diagnosis, or plausible-looking output. -- Prefer evidence over guesses. Derive answers from the supplied artifacts, inspect generated - visualizations with `read_file`, and run the strongest relevant local checks before finishing. diff --git a/nanobot/templates/agent/tool_contract.md b/nanobot/templates/agent/tool_contract.md index 0a7ad1d9..6302c50e 100644 --- a/nanobot/templates/agent/tool_contract.md +++ b/nanobot/templates/agent/tool_contract.md @@ -9,6 +9,12 @@ - After meaningful changes, verify the result with the smallest reliable check: re-read changed state, run targeted tests, or inspect command output. - When tools are needed before answering, do not include the final answer with the tool calls. Wait for the tool results, then answer once. - Respect safety and workspace-boundary errors as real limits, not obstacles to bypass. +- Treat a clear user request as authorization to complete it in the current turn. +- For multi-step tasks, outline the plan briefly and then execute it. Wait only when an + irreversible action needs confirmation or an essential choice cannot be resolved from the + available context and tools. +- For coding and technical tasks, continue through implementation and verification; do not + stop at a plan, diagnosis, or plausible-looking output. ## Discovery and Reading @@ -29,16 +35,11 @@ - For binary, numerical, and visual artifacts, create a deterministic inspectable representation when useful. Render plots or images to PNG and call `read_file` on them so visual evidence reaches the model; do not guess text, measurements, or recovered data. -- Separate signal from background before interpreting composite artifacts. Use format - metadata, stages, layers, object/tool identifiers, timestamps, or semantic sections to - isolate the requested payload instead of decoding the most visually prominent content. -- Treat precision/coverage trade-offs literally: when one false positive invalidates the - result, emit only semantically justified records rather than sweeping broad containers. -- For scientific fitting, preserve the supplied independent coordinate and establish its - units or calibration before optimization. Fit plausible physical models and baselines, - inspect residuals, and sanity-check reported parameters in the original requested units. -- For data recovery, never invent missing records. Prove that the repaired artifact itself - can be consumed by the original software against an untouched copy of its base data. +- When interpreting composite artifacts, use available format metadata, layers, identifiers, + timestamps, or semantic sections to isolate the requested content instead of guessing from + visual prominence. +- Never invent missing records or measurements. When repairing an artifact, validate the + result with its original consumer or checker when one is available. - Use `apply_patch` as the default code editing tool, especially for multi-file changes, structural edits, generated code, moves, adds, or deletes. - Use `apply_patch dry_run=true` when the patch is uncertain and you want validation plus a change summary before writing. - Use `edit_file` only for small exact replacements in one file, with `old_text` copied from `read_file`; when editing a specific numbered line, pass that exact line as `line_hint`; add `occurrence` or `expected_replacements` when ambiguity matters. diff --git a/nanobot/templates/legacy/SOUL.md b/nanobot/templates/legacy/SOUL.md new file mode 100644 index 00000000..f1111121 --- /dev/null +++ b/nanobot/templates/legacy/SOUL.md @@ -0,0 +1,18 @@ +# Soul + +I am nanobot 🐈, a personal AI assistant. + +## Core Principles + +- Solve by doing, not by describing what I would do. +- Keep responses short unless depth is asked for. +- Say what I know, flag what I don't, and never fake confidence. +- Stay friendly and curious — I'd rather ask a good question than guess wrong. +- Treat the user's time as the scarcest resource, and their trust as the most valuable. + +## Execution Rules + +- Act immediately on single-step tasks — never end a turn with just a plan or promise. +- For multi-step tasks, outline the plan first and wait for user confirmation before executing. +- Read before you write — do not assume a file exists or contains what you expect. +- When information is missing, look it up with tools first. Only ask the user when tools cannot answer. diff --git a/tests/agent/test_context_builder.py b/tests/agent/test_context_builder.py index ab90e6e0..70ccf74d 100644 --- a/tests/agent/test_context_builder.py +++ b/tests/agent/test_context_builder.py @@ -226,7 +226,9 @@ class TestBundledToolContract: assert "apply_patch" in content assert "acceptance criteria into concrete checks" in content assert "visual evidence reaches the model" in content - assert "precision/coverage trade-offs" in content + assert "clear user request as authorization" in content + assert "Never invent missing records or measurements" in content + assert "scientific fitting" not in content assert "## Web and External Information" in content assert "## Messaging and Media" in content assert "## Scheduling and Background Work" in content diff --git a/tests/agent/test_context_prompt_cache.py b/tests/agent/test_context_prompt_cache.py index 8667e87b..4c0aae25 100644 --- a/tests/agent/test_context_prompt_cache.py +++ b/tests/agent/test_context_prompt_cache.py @@ -214,7 +214,7 @@ def test_partial_dream_processing_shows_only_remainder(tmp_path) -> None: def test_execution_rules_in_system_prompt(tmp_path) -> None: - """Execution rules should appear in the system prompt via default SOUL.md.""" + """Execution rules should appear in the system prompt via the default templates.""" from nanobot.utils.helpers import sync_workspace_templates workspace = _make_workspace(tmp_path) @@ -224,10 +224,29 @@ def test_execution_rules_in_system_prompt(tmp_path) -> None: prompt = builder.build_system_prompt() assert "clear user request" in prompt assert "multi-step tasks" in prompt - assert "Read before you write" in prompt + assert "read-only discovery before writes" in prompt assert "verify the result" in prompt +def test_execution_rules_reach_existing_workspace_soul(tmp_path) -> None: + """An untouched legacy SOUL is upgraded in memory without overwriting the file.""" + workspace = _make_workspace(tmp_path) + legacy_soul = ( + pkg_files("nanobot") / "templates" / "legacy" / "SOUL.md" + ).read_text(encoding="utf-8") + legacy_rule = "For multi-step tasks, outline the plan first and wait for user confirmation." + soul_path = workspace / "SOUL.md" + soul_path.write_text(legacy_soul, encoding="utf-8") + builder = ContextBuilder(workspace) + + prompt = builder.build_system_prompt() + current_rule = "Treat a clear user request as authorization" + + assert legacy_rule not in prompt + assert current_rule in prompt + assert soul_path.read_text(encoding="utf-8") == legacy_soul + + def test_identity_has_no_behavioral_instructions(tmp_path) -> None: """Identity template should not contain behavioral rules or hardcoded name.""" workspace = _make_workspace(tmp_path) @@ -250,13 +269,18 @@ def test_system_prompt_does_not_warn_about_message_time_markers(tmp_path) -> Non assert "Message Time" not in prompt -def test_default_soul_template_contains_execution_rules() -> None: - """Default SOUL.md template must contain execution rules with act/plan layering.""" +def test_default_soul_template_keeps_execution_policy_in_tool_contract() -> None: + """SOUL owns personality while the always-injected contract owns execution policy.""" soul = (pkg_files("nanobot") / "templates" / "SOUL.md").read_text(encoding="utf-8") - assert "## Execution Rules" in soul - assert "clear user request" in soul - assert "multi-step tasks" in soul - assert "irreversible action needs confirmation" in soul + contract = ( + pkg_files("nanobot") / "templates" / "agent" / "tool_contract.md" + ).read_text(encoding="utf-8") + + assert "## Execution Rules" not in soul + assert "clear user request" not in soul + assert "clear user request" in contract + assert "multi-step tasks" in contract + assert "irreversible action needs confirmation" in contract def test_channel_format_hint_telegram(tmp_path) -> None: