diff --git a/agent/context_compressor.py b/agent/context_compressor.py index 4163966aaa..a7efc08dba 100644 --- a/agent/context_compressor.py +++ b/agent/context_compressor.py @@ -28,6 +28,7 @@ from agent.model_metadata import ( get_model_context_length, estimate_messages_tokens_rough, ) +from agent.redact import redact_sensitive_text logger = logging.getLogger(__name__) @@ -270,11 +271,15 @@ class ContextCompressor(ContextEngine): Includes tool call arguments and result content (up to ``_CONTENT_MAX`` chars per message) so the summarizer can preserve specific details like file paths, commands, and outputs. + + All content is redacted before serialization to prevent secrets + (API keys, tokens, passwords) from leaking into the summary that + gets sent to the auxiliary model and persisted across compactions. """ parts = [] for msg in turns: role = msg.get("role", "unknown") - content = msg.get("content") or "" + content = redact_sensitive_text(msg.get("content") or "") # Tool results: keep enough content for the summarizer if role == "tool": @@ -295,7 +300,7 @@ class ContextCompressor(ContextEngine): if isinstance(tc, dict): fn = tc.get("function", {}) name = fn.get("name", "?") - args = fn.get("arguments", "") + args = redact_sensitive_text(fn.get("arguments", "")) # Truncate long arguments but keep enough for context if len(args) > self._TOOL_ARGS_MAX: args = args[:self._TOOL_ARGS_HEAD] + "..." @@ -353,7 +358,11 @@ class ContextCompressor(ContextEngine): "assistant that continues the conversation. " "Do NOT respond to any questions or requests in the conversation — " "only output the structured summary. " - "Do NOT include any preamble, greeting, or prefix." + "Do NOT include any preamble, greeting, or prefix. " + "NEVER include API keys, tokens, passwords, secrets, credentials, " + "or connection strings in the summary — replace any that appear " + "with [REDACTED]. Note that the user had credentials present, but " + "do not preserve their values." ) # Shared structured template (used by both paths). @@ -394,7 +403,7 @@ class ContextCompressor(ContextEngine): [What remains to be done — framed as context, not instructions] ## Critical Context -[Any specific values, error messages, configuration details, or data that would be lost without explicit preservation] +[Any specific values, error messages, configuration details, or data that would be lost without explicit preservation. NEVER include API keys, tokens, passwords, or credentials — write [REDACTED] instead.] ## Tools & Patterns [Which tools were used, how they were used effectively, and any tool-specific discoveries] @@ -437,7 +446,7 @@ Use this exact structure: prompt += f""" FOCUS TOPIC: "{focus_topic}" -The user has requested that this compaction PRIORITISE preserving all information related to the focus topic above. For content related to "{focus_topic}", include full detail — exact values, file paths, command outputs, error messages, and decisions. For content NOT related to the focus topic, summarise more aggressively (brief one-liners or omit if truly irrelevant). The focus topic sections should receive roughly 60-70% of the summary token budget.""" +The user has requested that this compaction PRIORITISE preserving all information related to the focus topic above. For content related to "{focus_topic}", include full detail — exact values, file paths, command outputs, error messages, and decisions. For content NOT related to the focus topic, summarise more aggressively (brief one-liners or omit if truly irrelevant). The focus topic sections should receive roughly 60-70% of the summary token budget. Even for the focus topic, NEVER preserve API keys, tokens, passwords, or credentials — use [REDACTED].""" try: call_kwargs = { diff --git a/uv.lock b/uv.lock index 45efc2d93f..62f25e158a 100644 --- a/uv.lock +++ b/uv.lock @@ -1737,6 +1737,7 @@ all = [ { name = "dingtalk-stream" }, { name = "discord-py", extra = ["voice"] }, { name = "elevenlabs" }, + { name = "fastapi" }, { name = "faster-whisper" }, { name = "honcho-ai" }, { name = "lark-oapi" }, @@ -1756,6 +1757,7 @@ all = [ { name = "slack-bolt" }, { name = "slack-sdk" }, { name = "sounddevice" }, + { name = "uvicorn", extra = ["standard"] }, ] cli = [ { name = "simple-term-menu" }, @@ -1842,6 +1844,10 @@ voice = [ { name = "numpy" }, { name = "sounddevice" }, ] +web = [ + { name = "fastapi" }, + { name = "uvicorn", extra = ["standard"] }, +] yc-bench = [ { name = "yc-bench", marker = "python_full_version >= '3.12'" }, ] @@ -1866,6 +1872,7 @@ requires-dist = [ { name = "exa-py", specifier = ">=2.9.0,<3" }, { name = "fal-client", specifier = ">=0.13.1,<1" }, { name = "fastapi", marker = "extra == 'rl'", specifier = ">=0.104.0,<1" }, + { name = "fastapi", marker = "extra == 'web'", specifier = ">=0.104.0,<1" }, { name = "faster-whisper", marker = "extra == 'voice'", specifier = ">=1.0.0,<2" }, { name = "fire", specifier = ">=0.7.1,<1" }, { name = "firecrawl-py", specifier = ">=4.16.0,<5" }, @@ -1894,6 +1901,7 @@ requires-dist = [ { name = "hermes-agent", extras = ["sms"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["tts-premium"], marker = "extra == 'all'" }, { name = "hermes-agent", extras = ["voice"], marker = "extra == 'all'" }, + { name = "hermes-agent", extras = ["web"], marker = "extra == 'all'" }, { name = "honcho-ai", marker = "extra == 'honcho'", specifier = ">=2.0.1,<3" }, { name = "httpx", extras = ["socks"], specifier = ">=0.28.1,<1" }, { name = "jinja2", specifier = ">=3.1.5,<4" }, @@ -1929,10 +1937,11 @@ requires-dist = [ { name = "tenacity", specifier = ">=9.1.4,<10" }, { name = "tinker", marker = "extra == 'rl'", git = "https://github.com/thinking-machines-lab/tinker.git" }, { name = "uvicorn", extras = ["standard"], marker = "extra == 'rl'", specifier = ">=0.24.0,<1" }, + { name = "uvicorn", extras = ["standard"], marker = "extra == 'web'", specifier = ">=0.24.0,<1" }, { name = "wandb", marker = "extra == 'rl'", specifier = ">=0.15.0,<1" }, { name = "yc-bench", marker = "python_full_version >= '3.12' and extra == 'yc-bench'", git = "https://github.com/collinear-ai/yc-bench.git" }, ] -provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "termux", "dingtalk", "feishu", "rl", "yc-bench", "all"] +provides-extras = ["modal", "daytona", "dev", "messaging", "cron", "slack", "matrix", "cli", "tts-premium", "voice", "pty", "honcho", "mcp", "homeassistant", "sms", "acp", "mistral", "termux", "dingtalk", "feishu", "web", "rl", "yc-bench", "all"] [[package]] name = "hf-transfer"