diff --git a/backend/apps/agents/manager/prompt/attachments.py b/backend/apps/agents/manager/prompt/attachments.py
index 14e61e51..01233a02 100644
--- a/backend/apps/agents/manager/prompt/attachments.py
+++ b/backend/apps/agents/manager/prompt/attachments.py
@@ -148,11 +148,11 @@ def _resolve_attachments(context_paths: list | None, api_type: str, model: str)
# refused with concrete recovery actions.
b64_total = 0
- # Combined char budget across inline TEXT attachments. The per-file
- # 512K read cap doesn't stop a user dropping 20 huge txt files in one
- # turn and silently blowing the context window; this caps total inline
- # text chars and truncates the tail when we'd cross it. Picked to roughly
- # match 1M-window models (~375K tokens at 4 chars/token) while still
+ # Combined char budget across inline TEXT attachments. Per-file 512K read
+ # cap doesn't stop a user dropping 20 huge txt files in one turn and
+ # silently blowing the context window. Whole-file or refuse: partial files
+ # confuse the model and the user can't tell what's missing. Sized to
+ # roughly fit 1M-window models (~375K tokens at 4 chars/token) while
# leaving room for prior conversation, the prompt, and tool turns.
text_total_chars = 0
text_total_cap = 1_500_000
@@ -179,25 +179,19 @@ def _resolve_attachments(context_paths: list | None, api_type: str, model: str)
kind, media_type = _sniff_file_kind(head, os.path.basename(path))
if kind == "text":
- room = max(0, text_total_cap - text_total_chars)
- if room <= 0:
+ with open(path, "r", errors="replace") as f:
+ content = f.read(512_000)
+ if text_total_chars + len(content) > text_total_cap:
+ room = max(0, text_total_cap - text_total_chars)
refusals.append(
- f"[Attached text file {os.path.basename(path)} skipped: combined inline-text "
- f"budget of {text_total_cap // 1000}K chars already used by earlier attachments this turn. "
- f"Detach a file or split into separate turns.]"
+ f"[Attached text file {os.path.basename(path)} ({len(content) // 1000}K chars) "
+ f"skipped: would exceed combined text-attachment cap of {text_total_cap // 1000}K chars "
+ f"this turn (~{room // 1000}K left). Detach a file or split into separate turns.]"
)
continue
- with open(path, "r", errors="replace") as f:
- content = f.read(min(512_000, room))
- truncated_note = ""
- if len(content) >= room and os.path.getsize(path) > room:
- truncated_note = (
- f"\n[truncated: only first {room} chars included; "
- f"combined text-attachment cap of {text_total_cap // 1000}K chars reached.]"
- )
text_total_chars += len(content)
sections.append(
- f"\n{content}{truncated_note}\n"
+ f"\n{content}\n"
)
continue