#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 -- ` 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