summaryrefslogtreecommitdiffstats
path: root/src/compact.h
blob: c34a17d1e1aecc4e4d561cf514179e6b0a9bc775 (plain)
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