27 : max_tokens(max_tokens) {}
36int TokenCounter::count_text(
const std::string& text) {
40 return static_cast<int>(text.size()) / 4 + 1;
57 return count_text(
msg.content) + 4;
68 const std::vector<Message>& messages)
const {
70 for (
const auto&
msg : messages) {
84 const std::vector<Message>& messages)
const {
127 std::vector<Message>& messages,
129 const std::string& conversation_id) {
139 logger->warn(
"Context at {}/{} tokens, compaction disabled",
144 logger->info(
"Compacting conversation ({} tokens)",
current);
148 save_snapshot(conversation_id, messages);
153 auto compacted = compact(messages, summary,
stripped);
159 logger->error(
"Compaction did not reduce tokens: {} -> {}",
164 messages = std::move(compacted);
192 for (
size_t i = start;
i < messages.size(); ++
i) {
193 const auto&
msg = messages[
i];
194 auto src =
msg.metadata.find(
"source");
195 if (
src !=
msg.metadata.end() &&
src->second ==
"user") {
197 }
else if (
msg.role ==
"assistant") {
217 const std::vector<const Message*>&
user_msgs,
219 std::vector<Message> result;
222 for (
const auto*
m :
user_msgs) { result.push_back(*
m); }
238std::vector<Message> CompactionManager::compact(
239 const std::vector<Message>& messages,
240 std::string& summary,
244 if (!messages.empty() && messages[0].role ==
"system") {
254 std::vector<Message>
working(messages.begin() +
static_cast<long>(start),
256 logger->info(
"Compact: {} user, {} assistant, {} stripped",
258 summary = structured_summary(
working);
262 summary,
static_cast<int>(
working.size()));
275std::string CompactionManager::structured_summary(
276 const std::vector<Message>& messages) {
277 std::string
lines =
"Original task: "
278 + extract_original_task(messages);
280 auto tool_log = extract_tool_log(messages);
282 lines +=
"\n\nTool calls made (oldest first):";
305 if (
msg.content.rfind(
"Tool `", 0) == 0) {
return true; }
306 auto src =
msg.metadata.find(
"source");
307 return src !=
msg.metadata.end() &&
src->second ==
"tool";
319 const std::vector<Message>& messages,
320 const std::string& source) {
321 for (
const auto&
msg : messages) {
322 auto src =
msg.metadata.find(
"source");
323 if (
src !=
msg.metadata.end() &&
src->second == source) {
338 const std::vector<Message>& messages) {
339 for (
const auto&
msg : messages) {
340 if (
msg.role !=
"user" ||
msg.content.empty()) {
continue; }
361std::string CompactionManager::extract_original_task(
362 const std::vector<Message>& messages) {
365 if (task.empty()) {
return "(no user message found)"; }
366 if (task.size() > 500) {
return task.substr(0, 500) +
"..."; }
377std::vector<std::pair<std::string, std::string>>
378CompactionManager::extract_tool_log(
379 const std::vector<Message>& messages) {
380 std::vector<std::pair<std::string, std::string>> log;
381 for (
const auto&
msg : messages) {
382 auto it =
msg.metadata.find(
"tool_name");
383 if (
it ==
msg.metadata.end()) {
386 const auto& name =
it->second;
387 if (
msg.content.rfind(
"[Previous:", 0) == 0) {
388 log.emplace_back(name,
"(pruned)");
391 auto nl =
msg.content.find(
'\n');
392 std::string
brief =
msg.content.substr(0, std::min(
nl,
size_t{100}));
393 log.emplace_back(name,
brief);
406std::string CompactionManager::format_summary(
407 const std::string& summary,
409 return "[CONVERSATION SUMMARY]\n"
410 "The following summarizes "
411 + std::to_string(message_count)
412 +
" previous messages that have been compacted"
413 " to save context space.\n\n"
415 +
"\n\n[END SUMMARY - Recent conversation continues below]";
431 const std::vector<Message>& messages) {
436 auto compacted = compact(messages, summary,
stripped);
447 static_cast<int>(compacted.size()) - 1;
479 std::ostringstream
oss;
481 if (
c ==
'"')
oss <<
"\\\"";
482 else if (
c ==
'\\')
oss <<
"\\\\";
483 else if (
c ==
'\n')
oss <<
"\\n";
497 const std::vector<Message>& messages) {
498 std::ostringstream
oss;
500 for (
size_t i = 0;
i < messages.size(); ++
i) {
501 if (
i > 0)
oss <<
',';
502 oss <<
"{\"role\":\"" << messages[
i].role
503 <<
"\",\"content\":\"" <<
json_escape(messages[
i].content)
517void CompactionManager::save_snapshot(
518 const std::string& conversation_id,
519 const std::vector<Message>& messages) {
526 conversation_id.c_str(),
json_str.c_str(),
528 logger->info(
"Saved compaction snapshot for {} ({} messages)",
529 conversation_id, messages.size());
CompactionResult compact_messages(const std::vector< Message > &messages)
Compact messages using the value-density strategy.
CompactionConfig config
Compaction configuration.
TokenCounter & counter
Shared token counter.
CompactionResult check_and_compact(std::vector< Message > &messages, bool force=false, const std::string &conversation_id="")
Check if compaction is needed and perform if so.
void set_storage(const struct StorageInterface *storage)
Set storage interface for compaction snapshots.
CompactionManager(const CompactionConfig &config, TokenCounter &counter)
Construct a compaction manager.
Track token usage across conversation.
int max_tokens
Maximum context window size.
void clear_cache()
Clear the token count cache — retained as a no-op (gh#158).
int count_messages(const std::vector< Message > &messages) const
Count total tokens in a message list.
float usage_percent(const std::vector< Message > &messages) const
Get usage as fraction of context window (0.0–1.0).
TokenCounter(int max_tokens)
Construct a token counter.
int count_message(const Message &msg) const
Count tokens in a single message.
Auto-compaction for context management.
Types for the agentic loop engine.
json lines
Lines taken so far.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Activate model on GPU (WARM → ACTIVE).
static std::string find_tagged(const std::vector< Message > &messages, const std::string &source)
Find the first message matching a source tag.
static std::vector< Message > assemble_compacted(const Message *system_msg, Message summary_msg, const std::vector< const Message * > &user_msgs, const std::vector< const Message * > &assistant_msgs)
Assemble the compacted list: system, summary, users, last asst.
@ optional
optional: every escape asks the host's path approver
static void partition_messages(const std::vector< Message > &messages, size_t start, std::vector< const Message * > &user_msgs, std::vector< const Message * > &assistant_msgs, int &stripped_count)
Partition messages (from start) into user/assistant/stripped.
static std::string serialize_messages_json(const std::vector< Message > &messages)
Serialize messages to minimal JSON array.
static bool is_tool_result(const Message &msg)
Extract original user task from messages.
static std::string json_escape(const std::string &input)
Save pre-compaction snapshot via storage interface.
static std::string find_first_user_task(const std::vector< Message > &messages)
Find the first user-role message that isn't a tool result.
std::string config
–config FILE
Auto-compaction configuration.
bool save_full_history
Save full history before compaction.
float threshold_percent
Compaction trigger (0.5–0.99)
bool enabled
Enable auto-compaction.
Result of a compaction operation.
int messages_summarized
Messages stripped into summary.
std::string summary
Generated summary text.
std::vector< Message > messages
The compacted message list (v1.9.9)
int preserved_messages
Messages kept after compaction.
int old_token_count
Token count before compaction.
int new_token_count
Token count after compaction.
bool compacted
Whether compaction occurred.
std::string compactor_source
"default", "global_custom", or identity name
A message in a conversation.
Storage interface for conversation persistence.
bool(* save_snapshot)(const char *conversation_id, const char *messages_json, void *user_data)
Save a compaction snapshot (full history before compaction).
void * user_data
Opaque pointer (storage backend)