|
1 | | -"""Session limit enforcement (grace period, handoff triggers) for PreToolUse.""" |
| 1 | +"""Session limit enforcement — warn only, never stop. |
| 2 | +
|
| 3 | +Sessions continue through compaction. Limits produce warnings |
| 4 | +so the agent prioritizes current work over starting new tasks. |
| 5 | +No handoff, no stop, no restart — compaction handles context pressure. |
| 6 | +""" |
2 | 7 |
|
3 | | -from auto_handoff import trigger_auto_handoff |
4 | 8 | from session_state import ( |
5 | 9 | FALLBACK_MAX_EXCHANGES, |
6 | | - GRACE_TOOL_CALLS, |
7 | 10 | HARD_THRESHOLD_BYTES, |
8 | 11 | SessionState, |
9 | 12 | check_thresholds, |
|
12 | 15 |
|
13 | 16 |
|
14 | 17 | def apply_session_limits(state: SessionState) -> tuple: |
15 | | - """Apply byte/time/compaction limits. Returns (state, response_message). |
| 18 | + """Apply byte/time limits. Returns (state, response_message). |
16 | 19 |
|
17 | | - When continue_mode is False, limits only warn — no handoff, no stop. |
18 | | - Stopping a session without auto-restart is pointless. |
| 20 | + Never stops the session. Warns once when thresholds are hit |
| 21 | + so the agent wraps up current work instead of starting new tasks. |
| 22 | + Compaction handles context pressure naturally. |
19 | 23 | """ |
20 | 24 | triggered, stop_reason = check_thresholds(state) |
21 | 25 |
|
22 | | - if triggered and not state.continue_mode: |
23 | | - # No auto-restart — just warn, don't kill the session |
24 | | - if not state.warned: |
25 | | - state.warned = True |
26 | | - return state, ( |
27 | | - f"SESSION LIMIT WARNING: {stop_reason}. " |
28 | | - f"Auto-continuation is off, so the session will continue. " |
29 | | - f"Quality may degrade. Consider wrapping up current work." |
30 | | - ) |
31 | | - return state, "" |
32 | | - |
33 | | - if triggered: |
34 | | - return _handle_limit_triggered(state, stop_reason) |
| 26 | + if triggered and not state.warned: |
| 27 | + state.warned = True |
| 28 | + return state, ( |
| 29 | + f"SESSION LIMIT: {stop_reason}. " |
| 30 | + f"The session will continue (compaction handles context pressure). " |
| 31 | + f"Quality may degrade. Wrap up current work before starting new tasks." |
| 32 | + ) |
35 | 33 |
|
36 | 34 | if should_warn(state) and not state.warned: |
37 | 35 | state.warned = True |
38 | | - response = ( |
| 36 | + return state, ( |
39 | 37 | f"SESSION WARNING: {state.exchanges}/{FALLBACK_MAX_EXCHANGES} exchanges, " |
40 | 38 | f"{state.cumulative_output_bytes:,}/{HARD_THRESHOLD_BYTES:,} bytes. " |
41 | | - f"You are approaching the session limit. Finish current work " |
| 39 | + f"Approaching session limit. Finish current work " |
42 | 40 | f"and do not start new slabs or features." |
43 | 41 | ) |
44 | | - return state, response |
45 | 42 |
|
46 | 43 | return state, "" |
47 | | - |
48 | | - |
49 | | -def _handle_limit_triggered(state: SessionState, stop_reason: str) -> tuple: |
50 | | - """Handle limit when continue_mode is True — write handoff and stop.""" |
51 | | - is_time_triggered = "minutes" in stop_reason |
52 | | - |
53 | | - if is_time_triggered and state.stopped < 2: |
54 | | - state.stopped = 2 |
55 | | - trigger_auto_handoff(state, stop_reason) |
56 | | - return state, ( |
57 | | - f"SESSION TIME LIMIT: {stop_reason}.\n\n" |
58 | | - f"HANDOFF.md has been written automatically by the hook.\n" |
59 | | - f"A fresh session will be launched automatically.\n" |
60 | | - f"Finish your current task, then write HANDOFF.md." |
61 | | - ) |
62 | | - |
63 | | - if state.stopped == 0: |
64 | | - state.stopped = 1 |
65 | | - state.stop_at_tool_call = state.tool_calls + GRACE_TOOL_CALLS |
66 | | - return state, ( |
67 | | - f"SESSION LIMIT REACHED: {stop_reason}. " |
68 | | - f"You have {GRACE_TOOL_CALLS} tool calls remaining to wrap up.\n\n" |
69 | | - f"Finish your current task, then IMMEDIATELY:\n" |
70 | | - f"1. Write HANDOFF.md — current status, what's done (commits), " |
71 | | - f"what's next (spec text copied, not summarized), decisions, " |
72 | | - f"code change plan for next slab\n" |
73 | | - f"2. Update project-state.md Resume section\n" |
74 | | - f"3. A fresh session will be launched automatically after this one ends.\n\n" |
75 | | - f"Do NOT start new tasks. Finish current task and hand off." |
76 | | - ) |
77 | | - |
78 | | - grace_remaining = state.stop_at_tool_call - state.tool_calls |
79 | | - if grace_remaining <= 0: |
80 | | - if state.stopped != 2: |
81 | | - state.stopped = 2 |
82 | | - trigger_auto_handoff(state, stop_reason) |
83 | | - return state, ( |
84 | | - f"HARD STOP: Grace period exhausted. {stop_reason}.\n\n" |
85 | | - f"HANDOFF.md has been written automatically by the hook.\n" |
86 | | - f"A fresh session will be launched automatically." |
87 | | - ) |
88 | | - |
89 | | - return state, ( |
90 | | - f"SESSION LIMIT: {grace_remaining} tool calls remaining " |
91 | | - f"before hard stop. Finish current work and write HANDOFF.md." |
92 | | - ) |
0 commit comments