From c0a68acfc8102bf083ee776c882bcbd9cba02bb8 Mon Sep 17 00:00:00 2001 From: pewdiepie-archdaemon Date: Sun, 28 Jun 2026 21:49:09 +0000 Subject: [PATCH] Guard document style against persona guessing --- src/agent_loop.py | 21 +++++++++++++++++++++ src/settings.py | 4 ++++ tests/test_tool_policy.py | 30 ++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/src/agent_loop.py b/src/agent_loop.py index 0e5fb53fe..aef0048a1 100644 --- a/src/agent_loop.py +++ b/src/agent_loop.py @@ -1147,6 +1147,12 @@ def _build_system_prompt( if active_document: set_active_document(active_document.id) _doc_raw = active_document.current_content or "" + _document_writing_style = "" + try: + from src.settings import load_settings as _load_settings + _document_writing_style = (_load_settings().get("document_writing_style", "") or "").strip() + except Exception: + _document_writing_style = "" _doc_title_l = (active_document.title or "").strip().lower() _is_email_doc = ( active_document.language == "email" @@ -1237,6 +1243,21 @@ def _build_system_prompt( f'text must match the document EXACTLY and must NOT include the leading line-number ' f'or tab (those are reference-only). To rewrite entirely: update_document.' ) + if _document_writing_style: + doc_ctx += ( + "\n\nDOCUMENT WRITING STYLE — use only for normal prose writing/revision in this " + "document, not for code/data/JSON and not for email-specific greetings or signatures:\n" + f"{_document_writing_style}" + ) + else: + doc_ctx += ( + "\n\nStyle safety: if the user asks to write/rewrite this document \"in my style\" " + "or \"as my style\", do NOT infer that style from memories, identity, public persona, " + "creator/channel references, or biographical facts. There is no saved document writing " + "style. Ask the user for a style sample or a document writing style description before " + "rewriting for style. You may still make ordinary requested edits that do not depend on " + "knowing the user's personal style." + ) _doc_message = untrusted_context_message("active editor document", doc_ctx) _doc_message["_protected"] = True diff --git a/src/settings.py b/src/settings.py index f0db0e69e..0aa6cf08a 100644 --- a/src/settings.py +++ b/src/settings.py @@ -136,6 +136,10 @@ DEFAULT_SETTINGS = { "task_model": "", "default_endpoint_id": "", "default_model": "", + # Optional prose style used only for normal document writing/editing. + # Email replies use email_writing_style instead because greetings, + # signatures, and mailbox identity rules are medium-specific. + "document_writing_style": "", # Ordered fallback chain for the default chat model. Each entry is # {"endpoint_id": "...", "model": "..."}. If the primary model fails # before producing output (endpoint offline / errors), the chat diff --git a/tests/test_tool_policy.py b/tests/test_tool_policy.py index 177a667a4..aecbdce16 100644 --- a/tests/test_tool_policy.py +++ b/tests/test_tool_policy.py @@ -297,6 +297,36 @@ def test_guide_only_suppresses_active_document_context(monkeypatch): assert "Relevant skills" not in prompt_payloads[0] +def test_document_my_style_does_not_infer_public_persona(monkeypatch): + _patch_loop_basics(monkeypatch) + monkeypatch.setattr(al, "_build_base_prompt", lambda *a, **k: ("BASE", ""), raising=False) + monkeypatch.setattr(al, "_cached_base_prompt", None, raising=False) + monkeypatch.setattr(al, "_cached_base_prompt_key", None, raising=False) + + import src.settings as settings + monkeypatch.setattr(settings, "load_settings", lambda: {"document_writing_style": ""}, raising=False) + + active_doc = SimpleNamespace( + id="doc-style", + current_content="A short poem already exists here.", + title="Morning Poem", + language="markdown", + ) + + messages, _ = al._build_system_prompt( + [{"role": "user", "content": "Write as my style"}], + model="local-model", + active_document=active_doc, + mcp_mgr=None, + relevant_tools={"edit_document", "update_document"}, + suppress_skills=True, + ) + payload = "\n\n".join(str(msg.get("content", "")) for msg in messages) + + assert "There is no saved document writing style" in payload + assert "do NOT infer that style from memories, identity, public persona" in payload + + def test_guide_only_skips_teacher_escalation(monkeypatch): _patch_loop_basics(monkeypatch)