From d2732f10d20fd643aa62b47ddeec1e737d20cbdb Mon Sep 17 00:00:00 2001 From: Leul Negash Date: Sat, 5 Sep 2026 11:26:08 +0300 Subject: [PATCH 1/3] Order context sections stable to volatile so the cache prefix survives getContext placed LAST_SKILL_USE_RESULTS, which is rewritten every cycle and is one of the largest sections, ahead of HISTORY. Providers that cache prompts only reuse a prefix that is byte-identical to the previous request, so the reusable part ended right after the small static head and almost nothing was served from cache on consecutive calls. Add a stableContextOrder flag (default off) that moves HISTORY ahead of the volatile results section and keeps TIME last. With the flag off the assembled prompt is byte-for-byte identical to before, so behaviour is unchanged unless the flag is turned on. Refs #300 --- src/loop.metta | 18 ++++++++++++++++-- tests/src_loop.metta | 29 +++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 tests/src_loop.metta diff --git a/src/loop.metta b/src/loop.metta index 783b2daa..1fe987fa 100644 --- a/src/loop.metta +++ b/src/loop.metta @@ -7,11 +7,13 @@ (= (wakeupInterval) (empty)) (= (memoryDirectory) (empty)) (= (spamShield) (empty)) ; TODO: this parameter is considered deprecated +(= (stableContextOrder) (empty)) ; order prompt sections stable->volatile so providers can reuse the cached prefix (= (initLoop) (progn (configure maxNewInputLoops 50) ;20 (configure maxWakeLoops 1) (configure spamShield False) + (configure stableContextOrder False) (configure sleepInterval 1) ;10 (configure provider Anthropic) (configure maxOutputToken 6000) @@ -40,10 +42,22 @@ " toolName4 arg4" (newline) " toolName5 arg5" (newline) " SAVE_PERMANENT_FILES_DIR: " (memoryDirectory) - " LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback)) - " HISTORY: " (getHistory) + (contextVolatileTail) " TIME: " (get_time_as_string))))) +; LAST_SKILL_USE_RESULTS changes every cycle and is one of the largest sections, +; so keeping it before HISTORY ends the provider's reusable cache prefix right after +; the static head. With stableContextOrder on, HISTORY (which shifts far less between +; consecutive calls) comes first and the volatile results section goes last, so more of +; the prefix stays byte-identical and can be served from cache. Default is off, in which +; case the tail is byte-for-byte identical to the original layout. +(= (contextVolatileTail) + (if (stableContextOrder) + (py-str (" HISTORY: " (getHistory) + " LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback)))) + (py-str (" LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback)) + " HISTORY: " (getHistory))))) + (= (addTelegramPromptExtension) (if (== (commchannel) telegram) (let $path (joinPath ((memoryDirectory) "tg_prompt.txt")) diff --git a/tests/src_loop.metta b/tests/src_loop.metta new file mode 100644 index 00000000..0ff02dc9 --- /dev/null +++ b/tests/src_loop.metta @@ -0,0 +1,29 @@ +!(import! &self ./tests/lib/utils) +!(import! &self ../src/helper.py) +!(import! &self ./src/utils) +!(import! &self (library lib_llm.py)) +!(import! &self (library lib_llm.metta)) +!(import! &self ./src/loop) + +; Stub the sections contextVolatileTail reads so the ordering is the only variable. +(= (getHistory) "HIST") +(= (maxFeedback) 100) +!(change-state! &lastresults "RESULT") + +; Importing ./src/loop adds the (empty) placeholder; drop it so the flag has a +; single, deterministic value for each case (initLoop's configure does the same). +!(remove-atom &self (= (stableContextOrder) (empty))) + +; --- flag OFF (default): tail must stay byte-for-byte identical to the old layout --- +!(add-atom &self (= (stableContextOrder) False)) +!(test (contextVolatileTail) + (py-str (" LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback)) + " HISTORY: " (getHistory)))) +!(remove-atom &self (= (stableContextOrder) False)) + +; --- flag ON: HISTORY moves ahead of the volatile results section --- +!(add-atom &self (= (stableContextOrder) True)) +!(test (contextVolatileTail) + (py-str (" HISTORY: " (getHistory) + " LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback))))) +!(remove-atom &self (= (stableContextOrder) True)) From a6765f7d0e5955b3f8ba0da7e48bdd384c52221d Mon Sep 17 00:00:00 2001 From: Leul Negash Date: Sat, 5 Sep 2026 12:02:35 +0300 Subject: [PATCH 2/3] Make the getContext ordering test independent of loop state The first version of tests/src_loop.metta drove contextVolatileTail through the global stableContextOrder flag and the &lastresults state cell, toggling the flag with add-atom/remove-atom. That errored out under the PeTTa version CI runs and stopped the suite before any assertion ran. Split the ordering into a pure orderContextTail helper and test that directly with literal section strings, so no loop state, state cell or flag mutation is involved. getContext behaviour is unchanged: with the flag off the tail is still byte-for-byte identical to the original layout. --- src/loop.metta | 16 +++++++++++----- tests/src_loop.metta | 29 +++++++++-------------------- 2 files changed, 20 insertions(+), 25 deletions(-) diff --git a/src/loop.metta b/src/loop.metta index 1fe987fa..6a1cbdce 100644 --- a/src/loop.metta +++ b/src/loop.metta @@ -52,11 +52,17 @@ ; the prefix stays byte-identical and can be served from cache. Default is off, in which ; case the tail is byte-for-byte identical to the original layout. (= (contextVolatileTail) - (if (stableContextOrder) - (py-str (" HISTORY: " (getHistory) - " LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback)))) - (py-str (" LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback)) - " HISTORY: " (getHistory))))) + (orderContextTail (stableContextOrder) + (last_chars (get-state &lastresults) (maxFeedback)) + (getHistory))) + +; Section ordering, kept separate from the state reads above so it can be checked +; on its own. Flag off keeps the results section ahead of HISTORY (the original +; layout); flag on puts HISTORY first so the stable part sits in the cache prefix. +(= (orderContextTail $stable $results $history) + (if $stable + (py-str (" HISTORY: " $history " LAST_SKILL_USE_RESULTS: " $results)) + (py-str (" LAST_SKILL_USE_RESULTS: " $results " HISTORY: " $history)))) (= (addTelegramPromptExtension) (if (== (commchannel) telegram) diff --git a/tests/src_loop.metta b/tests/src_loop.metta index 0ff02dc9..84ee72fe 100644 --- a/tests/src_loop.metta +++ b/tests/src_loop.metta @@ -5,25 +5,14 @@ !(import! &self (library lib_llm.metta)) !(import! &self ./src/loop) -; Stub the sections contextVolatileTail reads so the ordering is the only variable. -(= (getHistory) "HIST") -(= (maxFeedback) 100) -!(change-state! &lastresults "RESULT") +; orderContextTail is a pure function of the two section strings, so the ordering +; can be checked directly without loop state or the global flag. -; Importing ./src/loop adds the (empty) placeholder; drop it so the flag has a -; single, deterministic value for each case (initLoop's configure does the same). -!(remove-atom &self (= (stableContextOrder) (empty))) +; Flag off (default): the volatile results section stays ahead of HISTORY, which +; keeps the assembled prompt byte-for-byte identical to the original layout. +!(test (orderContextTail False "RESULT" "HIST") + (py-str (" LAST_SKILL_USE_RESULTS: " "RESULT" " HISTORY: " "HIST"))) -; --- flag OFF (default): tail must stay byte-for-byte identical to the old layout --- -!(add-atom &self (= (stableContextOrder) False)) -!(test (contextVolatileTail) - (py-str (" LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback)) - " HISTORY: " (getHistory)))) -!(remove-atom &self (= (stableContextOrder) False)) - -; --- flag ON: HISTORY moves ahead of the volatile results section --- -!(add-atom &self (= (stableContextOrder) True)) -!(test (contextVolatileTail) - (py-str (" HISTORY: " (getHistory) - " LAST_SKILL_USE_RESULTS: " (last_chars (get-state &lastresults) (maxFeedback))))) -!(remove-atom &self (= (stableContextOrder) True)) +; Flag on: HISTORY moves ahead of the volatile results section. +!(test (orderContextTail True "RESULT" "HIST") + (py-str (" HISTORY: " "HIST" " LAST_SKILL_USE_RESULTS: " "RESULT"))) From 9cf33e450e2b6c96c9a001492ef1faf0dbe20e74 Mon Sep 17 00:00:00 2001 From: Leul Negash Date: Sat, 5 Sep 2026 12:16:38 +0300 Subject: [PATCH 3/3] Return the context ordering as a tuple so the test runs under CI's PeTTa Building the final string with py-str inside the test (and importing lib_llm to get it) hung under the PeTTa version CI runs and never reached an assertion. Return the ordered sections as a plain tuple from orderedContextSections and let contextVolatileTail wrap that in py-str, then assert on the tuple. The test no longer imports lib_llm or calls py-str. getContext output is unchanged. --- src/loop.metta | 19 ++++++++++--------- tests/src_loop.metta | 15 +++++++-------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/loop.metta b/src/loop.metta index 6a1cbdce..c0549482 100644 --- a/src/loop.metta +++ b/src/loop.metta @@ -52,17 +52,18 @@ ; the prefix stays byte-identical and can be served from cache. Default is off, in which ; case the tail is byte-for-byte identical to the original layout. (= (contextVolatileTail) - (orderContextTail (stableContextOrder) - (last_chars (get-state &lastresults) (maxFeedback)) - (getHistory))) + (py-str (orderedContextSections (stableContextOrder) + (last_chars (get-state &lastresults) (maxFeedback)) + (getHistory)))) -; Section ordering, kept separate from the state reads above so it can be checked -; on its own. Flag off keeps the results section ahead of HISTORY (the original -; layout); flag on puts HISTORY first so the stable part sits in the cache prefix. -(= (orderContextTail $stable $results $history) +; Section ordering, kept separate from the state reads and the py-str call so it +; can be checked on its own. Returns the sections as a plain tuple. Flag off keeps +; the results section ahead of HISTORY (the original layout); flag on puts HISTORY +; first so the stable part sits in the reusable cache prefix. +(= (orderedContextSections $stable $results $history) (if $stable - (py-str (" HISTORY: " $history " LAST_SKILL_USE_RESULTS: " $results)) - (py-str (" LAST_SKILL_USE_RESULTS: " $results " HISTORY: " $history)))) + (" HISTORY: " $history " LAST_SKILL_USE_RESULTS: " $results) + (" LAST_SKILL_USE_RESULTS: " $results " HISTORY: " $history))) (= (addTelegramPromptExtension) (if (== (commchannel) telegram) diff --git a/tests/src_loop.metta b/tests/src_loop.metta index 84ee72fe..36e9657a 100644 --- a/tests/src_loop.metta +++ b/tests/src_loop.metta @@ -1,18 +1,17 @@ !(import! &self ./tests/lib/utils) !(import! &self ../src/helper.py) !(import! &self ./src/utils) -!(import! &self (library lib_llm.py)) -!(import! &self (library lib_llm.metta)) !(import! &self ./src/loop) -; orderContextTail is a pure function of the two section strings, so the ordering -; can be checked directly without loop state or the global flag. +; orderedContextSections is a pure function of the two section strings and returns +; the sections as a plain tuple, so the ordering can be checked directly without +; loop state, the global flag, or building the final string. ; Flag off (default): the volatile results section stays ahead of HISTORY, which ; keeps the assembled prompt byte-for-byte identical to the original layout. -!(test (orderContextTail False "RESULT" "HIST") - (py-str (" LAST_SKILL_USE_RESULTS: " "RESULT" " HISTORY: " "HIST"))) +!(test (orderedContextSections False "RESULT" "HIST") + (" LAST_SKILL_USE_RESULTS: " "RESULT" " HISTORY: " "HIST")) ; Flag on: HISTORY moves ahead of the volatile results section. -!(test (orderContextTail True "RESULT" "HIST") - (py-str (" HISTORY: " "HIST" " LAST_SKILL_USE_RESULTS: " "RESULT"))) +!(test (orderedContextSections True "RESULT" "HIST") + (" HISTORY: " "HIST" " LAST_SKILL_USE_RESULTS: " "RESULT"))