1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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
|
#ifndef LLSH_COMPACT_H
#define LLSH_COMPACT_H
#include "message.h"
#include "util.h"
/* Mirrors compact.rs: pure helpers for the two-tier context compaction.
* Side-effect free (no I/O) so it is unit-testable; the file reads and the
* model call live in the session. */
#define CLEARED_PLACEHOLDER \
"[tool output cleared to save context; re-run the tool if you need it]"
/* Re-injected file caps: per-file chars, number of files, total budget. */
#define PER_FILE_CAP 50000UL
#define MAX_FILES 5UL
#define FILES_CAP 100000UL
/* System preamble for the tool-less one-shot summarizer. */
const char *compact_summarizer_preamble(void);
/* The resolved compaction knobs, carried by a session. */
typedef struct {
unsigned long context_limit; /* 0 disables compaction entirely */
double compact_threshold; /* fraction; 0.0 disables the tier */
double clear_threshold; /* fraction; 0.0 disables the tier */
} compact_config;
/* Populate from a config (see config.h). */
void compact_config_from(unsigned long context_limit, double compact_threshold,
double clear_threshold, compact_config *out);
/* Token budget at which the summarization tier fires.
* Returns true (and sets *out) when enabled, false when disabled. */
bool compact_at(const compact_config *c, unsigned long *out);
bool clear_at(const compact_config *c, unsigned long *out);
/* Which compaction action actually ran. */
typedef enum {
TIER_CLEARED, /* tier 1: stale tool results replaced in place */
TIER_SUMMARIZED, /* tier 2: prefix summarized, recent files re-injected */
TIER_FALLBACK_CLEAR /* tier 2 requested but summarization failed */
} compact_tier;
/* What a compaction pass did, for a status line and tests. */
typedef struct {
compact_tier tier;
unsigned long tokens_before;
unsigned long tokens_after;
size_t cleared_tool_results; /* 0 when a summarization ran */
size_t files_reinjected; /* 0 unless a summarization ran */
} compact_report;
/* Rough token count: total bytes / 4 (div_ceil). */
unsigned long compact_estimate_tokens(const message *msgs, size_t n);
/* Index of the last pure-text user message, or n when there is none. */
size_t compact_keep_boundary(const message *msgs, size_t n);
/* Replace content of stale tool results (strictly before boundary) with the
* placeholder. Idempotent. Returns results cleared on this pass. */
size_t compact_clear_old_tool_results(message *msgs, size_t n, size_t boundary);
/* Flatten a message slice into a plain-text transcript. Caller frees. */
char *compact_transcript(const message *msgs, size_t n);
/* Render the conversation as a Markdown transcript. Caller frees. */
char *compact_transcript_markdown(const message *msgs, size_t n);
/* Extract file paths listed under a `## Files` heading (case-insensitive;
* accepts `-` or `*` bullets). Caller frees the strlist. */
void compact_recent_file_paths(const char *summary, strlist *out);
/* Build the post-compaction history: one user message carrying the summary
* and any re-injected files, followed by the kept tail (copied).
* Heap-allocated: caller frees with history_free(h); free(h); */
history *compact_compose_history(const char *summary,
const char **file_paths,
const char **file_contents, size_t nfiles,
const message *tail, size_t ntail);
/* ------------------------------------------------------------------ */
/* SelfCompact-style rubric probe (arXiv:2606.23525) */
/* ------------------------------------------------------------------ */
/* Fire-gate thresholds from the paper. max_summaries 0 = unlimited. */
#define RUBRIC_MIN_ITERATION 3UL
#define RUBRIC_MIN_TOKENS 40000UL
#define RUBRIC_MIN_ROUNDS_SINCE_PROBE 2UL
#define RUBRIC_DEFAULT_MAX_SUMMARIES 1UL
/* Verdict of a rubric probe. Each field: 1 = Y, 0 = N. */
typedef struct {
int c1; /* CLOSED-UNIT: last assistant message is a closed unit */
int c2; /* SUMMARIZABLE: value fits 3-5 citable facts */
int c3; /* PROGRESS: something advanced since last compression */
int n1; /* STUCK: recent tool calls yield nothing new */
} compact_rubric;
/* System preamble for the rubric judge (kept out of the summarizer's
* "write a summary" role). */
const char *compact_rubric_preamble(void);
/* The probe prompt (the paper's search rubric, adapted to tool calls). */
const char *compact_rubric_prompt(void);
/* Parse the four `C1/C2/C3/N1: Y|N -- <evidence>` lines. A Y without
* evidence on its line is downgraded to N (the paper's rule). `out` must
* be non-NULL; 0 on success, -1 with owned *err when a verdict is missing
* or malformed. */
int compact_rubric_parse(const char *answer, compact_rubric *out, char **err);
/* Fire only when C1 and C2 and C3 are all Y and N1 is N. */
bool compact_rubric_fire(const compact_rubric *r);
/* Gate check: iteration (tool rounds) >= 3, prompt >= 40k tokens,
* summaries so far < cap (0 = unlimited), and >= 2 tool rounds since the
* last probe. */
bool compact_rubric_due(unsigned long iteration, unsigned long prompt_tokens,
unsigned long summaries_so_far,
unsigned long max_summaries,
unsigned long rounds_since_probe);
#endif
|