symfonic.agent.history_trim¶
history_trim ¶
Conversation-history trimming shared by the legacy and kernel routes.
Extracted verbatim from symfonic.agent.engine so the migrated kernel route
(:func:symfonic.agent.cutover.messages.as_kernel_history) can trim without
importing the engine module. engine retains lazy wrappers under the
original private names, so the legacy body and every existing import of
symfonic.agent.engine._pair_aware_history_slice / _log_history_trim
keep working through the same implementations.
The logger name is "symfonic.agent" -- the engine's own hardcoded logger
name -- so the DEBUG record keeps its identity for operators filtering on it.
log_history_trim ¶
DEBUG-log how many oldest history messages cap is about to drop.
Added in v7.1.2 so operator-side "memory loss" reports can be told apart
from retrieval/consolidation losses. Module-level rather than a method
because both invocation routes have to emit it: the legacy body calls it
through :meth:SymfonicAgent._maybe_log_history_trim, and the migrated
kernel route calls it from
:func:symfonic.agent.cutover.messages.as_kernel_history. A route that
trims silently while the other one narrates the same trim is an observable
difference on the one diagnostic that exists for this class of report.
Source code in src/symfonic/agent/history_trim.py
pair_aware_history_slice ¶
v7.12.0: cap a conversation-history list to the most recent
cap entries WITHOUT cutting between an AIMessage's
tool_use block and its matching ToolMessage.
Background. Anthropic's API rejects (HTTP 400) any message list
where an AIMessage carries a tool_use block without a
following tool_result block with the matching
tool_use_id. The pre-v7.12.0 slice history[-cap:] was
blunt -- it would happily cut between an AIMessage and its
ToolMessage, leaving the leading message dangling and producing
a 400 on the next call. With v7.12.0 tool-result compaction
shrinking individual ToolMessages, more messages fit under the
cap and this latent bug surfaced more often.
Algorithm. Walk from the end of history backwards. Keep
messages until the count >= cap. After hitting cap, continue
walking back through any AIMessage whose tool_calls are not
all paired by ToolMessages already in the retained slice. This
guarantees every retained AIMessage's tool_use blocks have a
matching ToolMessage in the slice.
Defensive: returns history[-cap:] unchanged when cap >=
len(history) (no slicing needed) and when cap <= 0
(returns empty list, matching the pre-existing semantics).
Source code in src/symfonic/agent/history_trim.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 | |