Commit Diff


commit - 8e964caf96f4c4f928cb51a7f4fd99288ec87914
commit + 3f64e350370dd21067666d57867a764e16479365
blob - e9067ff4fafbb22cbc7d3f953d6bddb9c041a41f
blob + 015c7389169c6956ee20f00cc12e128beb8a7b75
--- regress/conf/conf_test.c
+++ regress/conf/conf_test.c
@@ -132,6 +132,9 @@ test_defaults(void)
 	CHECK(c->initial_provider_slot == 0);
 	CHECK(c->max_tokens == 4096);
 	CHECK(c->context_limit == 0);
+	CHECK(c->auto_compact == 1);
+	CHECK(c->compact_reserve_tokens == 16384);
+	CHECK(c->compact_keep_tokens == 20000);
 	CHECK(c->allow_write == 1);
 	CHECK(c->allow_subprocess_net == 0);
 	CHECK(c->web_search == 1);
@@ -160,6 +163,9 @@ test_globals(void)
 	    "provider \"openai\"\n"
 	    "max_tokens 8192\n"
 	    "context_limit 100000\n"
+	    "auto_compact no\n"
+	    "compact_reserve_tokens 12345\n"
+	    "compact_keep_tokens 23456\n"
 	    "allow_write no\n"
 	    "web_search no\n"
 	    "notify no\n"
@@ -176,6 +182,9 @@ test_globals(void)
 	CHECK(strcmp(conf_api_port(c), "8443") == 0);
 	CHECK(c->max_tokens == 8192);
 	CHECK(c->context_limit == 100000);
+	CHECK(c->auto_compact == 0);
+	CHECK(c->compact_reserve_tokens == 12345);
+	CHECK(c->compact_keep_tokens == 23456);
 	CHECK(c->allow_write == 0);
 	CHECK(c->web_search == 0);
 	CHECK(c->notify == 0);
@@ -558,10 +567,24 @@ test_macros(void)
 static void
 test_numbers(void)
 {
+	struct fugu_conf	*c;
+
 	CHECK(parse_fails("max_tokens 0\n"));
 	CHECK(parse_fails("max_tokens -5\n"));
 	CHECK(parse_fails("max_tokens 999999999999999999999999\n"));
 	CHECK(parse_fails("context_limit 0\n"));
+	CHECK(parse_fails("compact_reserve_tokens 0\n"));
+	CHECK(parse_fails("compact_reserve_tokens -1\n"));
+	CHECK(parse_fails("compact_reserve_tokens 1000000001\n"));
+	CHECK(parse_fails("compact_keep_tokens 0\n"));
+	CHECK(parse_fails("compact_keep_tokens -1\n"));
+	CHECK(parse_fails("compact_keep_tokens 1000000001\n"));
+	CHECK(parse_ok(
+	    "compact_reserve_tokens 1\n"
+	    "compact_keep_tokens 1000000000\n", &alice, &c));
+	CHECK(c->compact_reserve_tokens == 1);
+	CHECK(c->compact_keep_tokens == 1000000000);
+	conf_free(c);
 	CHECK(parse_fails("max_subagents -1\n"));
 	CHECK(parse_fails("max_subagents 1000\n"));
 	CHECK(parse_fails("max_bg_jobs -1\n"));
@@ -701,6 +724,29 @@ test_include(void)
 }
 
 static void
+test_dump_compaction(void)
+{
+	struct fugu_conf	*c;
+	FILE			*f;
+	char			*buf = NULL;
+	size_t			 sz = 0;
+
+	CHECK(parse_ok(
+	    "auto_compact no\n"
+	    "compact_reserve_tokens 123\n"
+	    "compact_keep_tokens 456\n", &alice, &c));
+	if ((f = open_memstream(&buf, &sz)) == NULL)
+		err(1, "open_memstream");
+	conf_dump(c, f);
+	fclose(f);
+	CHECK(strstr(buf, "auto_compact no\n") != NULL);
+	CHECK(strstr(buf, "compact_reserve_tokens 123\n") != NULL);
+	CHECK(strstr(buf, "compact_keep_tokens 456\n") != NULL);
+	free(buf);
+	conf_free(c);
+}
+
+static void
 test_dump_redacts(void)
 {
 	struct fugu_conf	*c;
@@ -816,6 +862,7 @@ main(void)
 	test_model_bounds();
 	test_hostile();
 	test_include();
+	test_dump_compaction();
 	test_dump_redacts();
 	test_wipe_delegated_secrets();
 	test_lifetimes();
blob - 6c0eb932abd0c346232a70ae4cdd3c73f791f51d
blob + 5e66360b3f782e643849f899753939f640399269
--- src/fugu/conf.c
+++ src/fugu/conf.c
@@ -40,6 +40,9 @@ conf_new(void)
 	c->model = xstrdup("claude-sonnet-4-6");
 	c->provider_type = PROVIDER_ANTHROPIC;
 	c->max_tokens = 4096;
+	c->auto_compact = 1;
+	c->compact_reserve_tokens = FUGU_COMPACT_RESERVE_TOKENS_DEFAULT;
+	c->compact_keep_tokens = FUGU_COMPACT_KEEP_TOKENS_DEFAULT;
 	c->allow_write = 1;
 	c->web_search = 1;
 	c->search_provider = xstrdup("kagi");
@@ -274,6 +277,11 @@ conf_dump(const struct fugu_conf *c, FILE *f)
 	if (c->context_limit > 0)
 		fprintf(f, "context_limit %lld\n",
 		    (long long)c->context_limit);
+	fprintf(f, "auto_compact %s\n", yesno(c->auto_compact));
+	fprintf(f, "compact_reserve_tokens %lld\n",
+	    (long long)c->compact_reserve_tokens);
+	fprintf(f, "compact_keep_tokens %lld\n",
+	    (long long)c->compact_keep_tokens);
 	fprintf(f, "allow_write %s\n", yesno(c->allow_write));
 	fprintf(f, "allow_subprocess_net %s\n",
 	    yesno(c->allow_subprocess_net));
blob - 4f704363a3ed8dae87743361ec043cbd9f0813cd
blob + ad1a542cfbdd0add698e93840eafde82b407b89d
--- src/fugu/conf.h
+++ src/fugu/conf.h
@@ -23,6 +23,8 @@
 #include <stdio.h>
 
 #define FUGU_CONF_PATH		"/etc/fugu.conf"
+#define FUGU_COMPACT_RESERVE_TOKENS_DEFAULT	INT64_C(16384)
+#define FUGU_COMPACT_KEEP_TOKENS_DEFAULT	INT64_C(20000)
 
 /* Unambiguous subagent_provider spelling for the implicit provider. */
 #define FUGU_IMPLICIT_PROVIDER_REF	"@default"
@@ -63,6 +65,9 @@ struct fugu_conf {
 	char			*system;	/* NULL: built-in prompt */
 	int64_t			 max_tokens;
 	int64_t			 context_limit;	/* 0: unset */
+	int			 auto_compact;
+	int64_t			 compact_reserve_tokens;
+	int64_t			 compact_keep_tokens;
 	int			 allow_write;
 	int			 allow_subprocess_net;
 	int			 web_search;
blob - 1b7a16b8769e9b408730b8ed5e4c7c729edf5313
blob + bbc3a96f14f004077b95fe4bca03b4954f3b0adf
--- src/fugu/fugu.conf.5
+++ src/fugu/fugu.conf.5
@@ -234,10 +234,35 @@ Markdown structure is retained and unsupported glyphs 
 .Sq ? .
 The default is
 .Ic no .
+.It Cm auto_compact Ic yes | no
+Automatically Compact the Lead Projection before a Generation when its
+estimated size exceeds the Effective window less
+.Cm compact_reserve_tokens .
+When enabled, a recognized Provider context-overflow rejection may also
+Compact and retry the rejected Generation once.
+With
+.Ic no ,
+neither automatic path runs;
+.Cm /compact
+remains available.
+The default is
+.Ic yes .
+.It Cm compact_keep_tokens Ar tokens
+Set the target size of the recent Projection suffix retained by automatic and
+manual Compaction.
+The value must be between 1 and 1000000000.
+The default is 20000.
+.It Cm compact_reserve_tokens Ar tokens
+Reserve this much of the Effective window when deciding whether to Compact
+before a Lead Generation.
+The value must be between 1 and 1000000000.
+The default is 16384.
 .It Cm context_limit Ar tokens
 Set an advisory projection budget and the effective window displayed by
 .Cm /context
 and the curses status bar.
+When set, it is also the first-priority Effective-window override used by
+automatic Compaction.
 The value must be between 1 and 1000000000.
 By default it is unset, and provider metadata or a compiled model table is
 used when available.
blob - 21ec4235f66e401447251a9e19e7c6e0509a3ee9
blob + dc06ddff4712f16bda5f125ac86d2bd40df46300
--- src/fugu/parse.y
+++ src/fugu/parse.y
@@ -131,7 +131,8 @@ typedef struct {
 %}
 
 %token	ALLOWSUBPROCESSNET ALLOWWRITE ANTHROPICKEY ANTHROPICKEYFILE APIHOST
-%token	APIKEY APIKEYFILE APIPATH APIPORT ASCII CONTEXTLIMIT ERROR GROUP
+%token	APIKEY APIKEYFILE APIPATH APIPORT ASCII AUTOCOMPACT
+%token	COMPACTKEEPTOKENS COMPACTRESERVETOKENS CONTEXTLIMIT ERROR GROUP
 %token	HTTPALLOW HTTPBLOCK INCLUDE KAGITOKEN KAGITOKENFILE MATCH MAXBGJOBS
 %token	MAXSUBAGENTS MAXTOKENS MODEL NO NOTIFY OAUTHTOKEN
 %token	PROJECTCONTEXT PROTECT
@@ -222,6 +223,23 @@ main		: MODEL STRING			{
 			}
 			set_number(&conf->context_limit, $2);
 		}
+		| AUTOCOMPACT yesno		{
+			set_bool(&conf->auto_compact, $2);
+		}
+		| COMPACTRESERVETOKENS NUMBER	{
+			if ($2 < 1 || $2 > 1000000000) {
+				yyerror("compact_reserve_tokens out of range");
+				YYERROR;
+			}
+			set_number(&conf->compact_reserve_tokens, $2);
+		}
+		| COMPACTKEEPTOKENS NUMBER	{
+			if ($2 < 1 || $2 > 1000000000) {
+				yyerror("compact_keep_tokens out of range");
+				YYERROR;
+			}
+			set_number(&conf->compact_keep_tokens, $2);
+		}
 		| ALLOWWRITE yesno		{
 			set_bool(&conf->allow_write, $2);
 		}
@@ -588,6 +606,9 @@ lookup(char *s)
 		{ "api_path",			APIPATH },
 		{ "api_port",			APIPORT },
 		{ "ascii",			ASCII },
+		{ "auto_compact",		AUTOCOMPACT },
+		{ "compact_keep_tokens",	COMPACTKEEPTOKENS },
+		{ "compact_reserve_tokens",	COMPACTRESERVETOKENS },
 		{ "context_limit",		CONTEXTLIMIT },
 		{ "group",			GROUP },
 		{ "http_allow",			HTTPALLOW },