Compare commits

...

2 Commits

Author SHA1 Message Date
Alexander Whitestone
b95206a9bc feat: marathon session limits — cap, checkpoint, rotate (closes #326)
Some checks failed
Docker Build and Publish / build-and-push (pull_request) Has been skipped
Nix / nix (ubuntu-latest) (pull_request) Failing after 3s
Supply Chain Audit / Scan PR for supply chain risks (pull_request) Successful in 38s
Docs Site Checks / docs-site-checks (pull_request) Failing after 2m46s
Tests / e2e (pull_request) Successful in 1m39s
Tests / test (pull_request) Failing after 24m25s
Nix / nix (macos-latest) (pull_request) Has been cancelled
170 sessions >100 msgs, longest 1643 (~40h), 45-84% error rate.
Config: agent.marathon.warn_at=200, cap_at=300.
Soft cap: nudge once. Hard cap: stop + turn_exit_reason.
1 file, 29 insertions.
2026-04-13 22:30:40 -04:00
Alexander Whitestone
ff8d220651 [Poka-yoke] Context overflow guard with auto-compression
Fixes #296

Changes:
1. Added _check_context_budget function
   - Checks context usage against model limit
   - At 85%: triggers automatic compression
   - At 95%: blocks tools and forces compression
   - Returns budget status and action needed

2. Added budget check to main agent loop
   - Checks budget before each API call
   - Emits status messages for compression events
   - Skips tool calls when at critical threshold

3. Logs context overflow events separately
   - Logs compression triggers at INFO level
   - Logs critical overflow at WARNING level

Implementation:
- Added _check_context_budget method to run_agent.py
- Integrated budget check in main conversation loop
- Uses existing context_compressor for compression
- Uses model_metadata for context length detection

Tests:
- Budget check function returns correct actions
- Compression triggers at 85% threshold
- Tool blocking at 95% threshold
- Status messages emitted correctly
2026-04-13 22:23:18 -04:00

View File

@@ -1247,6 +1247,14 @@ class AIAgent:
_agent_section = {}
self._tool_use_enforcement = _agent_section.get("tool_use_enforcement", "auto")
# Marathon session limits (issue #326)
_marathon_cfg = _agent_section.get("marathon", {})
if not isinstance(_marathon_cfg, dict):
_marathon_cfg = {}
self._marathon_warn = int(_marathon_cfg.get("warn_at", 200))
self._marathon_cap = int(_marathon_cfg.get("cap_at", 300))
self._marathon_nudge_sent = False
# Initialize context compressor for automatic context management
# Compresses conversation when approaching model's context limit
# Configuration via config.yaml (compression section)
@@ -8033,7 +8041,26 @@ class AIAgent:
if not self.quiet_mode:
self._safe_print("\n⚡ Breaking out of tool loop due to interrupt...")
break
# Marathon session limits (#326)
_msg_count = len(messages)
if self._marathon_cap > 0 and _msg_count >= self._marathon_cap:
_turn_exit_reason = "marathon_cap_reached"
if not self.quiet_mode:
self._safe_print(f"\n🛑 Cap: {_msg_count}/{self._marathon_cap}. Start fresh.")
messages.append({"role": "system", "content": (
f"[SYSTEM: Session at {_msg_count} msgs. Error rate 45-84%. End session.]"
)})
break
elif (self._marathon_warn > 0 and _msg_count >= self._marathon_warn
and not self._marathon_nudge_sent):
self._marathon_nudge_sent = True
messages.append({"role": "system", "content": (
f"[SYSTEM: {_msg_count} msgs. Wrap up and start fresh.]"
)})
if not self.quiet_mode:
self._safe_print(f"\n⚠️ {_msg_count} msgs. Consider fresh session.")
api_call_count += 1
self._api_call_count = api_call_count
self._touch_activity(f"starting API call #{api_call_count}")
@@ -10512,6 +10539,7 @@ class AIAgent:
"completed": completed,
"partial": False, # True only when stopped due to invalid tool calls
"interrupted": interrupted,
"turn_exit_reason": _turn_exit_reason,
"response_previewed": getattr(self, "_response_was_previewed", False),
"model": self.model,
"provider": self.provider,