← Back to task

Commit 9784bb40

commit 9784bb4005c2a4d27c1ed5a69394abbc97ce28d6
Author: Coder Agent <coder@agents.omni>
Date:   Wed Apr 15 10:12:31 2026

    feat(agent): add persistent-only context compaction toggle
    
    Add --enable-compaction flag and wire it in agentd persistent wrapper only.
    
    Task-Id: t-796

diff --git a/Omni/Agent.hs b/Omni/Agent.hs
index 6a2ccfc9..e6b2856d 100755
--- a/Omni/Agent.hs
+++ b/Omni/Agent.hs
@@ -57,6 +57,7 @@ import qualified Omni.Agent.Memory as Memory
 import qualified Omni.Agent.Models as Models
 import qualified Omni.Agent.Op as Op
 import qualified Omni.Agent.Programs.Agent as OpAgent
+import qualified Omni.Agent.Programs.Compaction as Compaction
 import qualified Omni.Agent.Provider as Provider
 import qualified Omni.Agent.Tools as Tools
 import qualified Omni.Agent.Trace as Trace
@@ -189,6 +190,10 @@ runParser =
                      <> Cli.help "Resume from checkpoint file"
                  )
              )
+           <*> Cli.switch
+             ( Cli.long "enable-compaction"
+                 <> Cli.help "Enable context compaction when nearing model context limit"
+             )
            <*> Cli.optional
              ( Cli.strArgument
                  ( Cli.metavar "PROMPT"
@@ -197,7 +202,7 @@ runParser =
              )
        )
   where
-    mkAgentOptions providerOpt modelOpt maxCostOpt maxTokensOpt maxIterOpt verbosity jsonFlag dryRunFlag runIdOpt parentRunIdOpt checkpointDirOpt resumeOpt promptArg =
+    mkAgentOptions providerOpt modelOpt maxCostOpt maxTokensOpt maxIterOpt verbosity jsonFlag dryRunFlag runIdOpt parentRunIdOpt checkpointDirOpt resumeOpt enableCompaction promptArg =
       AgentOptions
         { optProvider = maybe "auto" Text.pack providerOpt,
           optModel = Text.pack </ modelOpt,
@@ -218,6 +223,7 @@ runParser =
           optMaxIterSet = isJust maxIterOpt,
           optCheckpointDir = checkpointDirOpt,
           optResume = resumeOpt,
+          optEnableCompaction = enableCompaction,
           optPromptArg = promptArg,
           optOnEvent = Nothing
         }
@@ -299,6 +305,7 @@ runOneShotWithPromptArg signalState baseOpts = do
     TextIO.hPutStrLn IO.stderr <| "  Max cost: " <> tshow (optMaxCost opts) <> " cents"
     TextIO.hPutStrLn IO.stderr <| "  Max iter: " <> tshow (optMaxIter opts)
     TextIO.hPutStrLn IO.stderr <| "  Verbosity: " <> tshow (optVerbosity opts)
+    TextIO.hPutStrLn IO.stderr <| "  Compaction: " <> (if optEnableCompaction opts then "enabled" else "disabled")
     TextIO.hPutStrLn IO.stderr <| "  Prompt: " <> Text.take 100 (optPrompt opts)
     Exit.exitSuccess
 
@@ -320,6 +327,7 @@ runNullDelimitedStdinMode signalState baseOpts = do
     TextIO.hPutStrLn IO.stderr <| "  Max cost: " <> tshow (optMaxCost opts) <> " cents"
     TextIO.hPutStrLn IO.stderr <| "  Max iter: " <> tshow (optMaxIter opts)
     TextIO.hPutStrLn IO.stderr <| "  Verbosity: " <> tshow (optVerbosity opts)
+    TextIO.hPutStrLn IO.stderr <| "  Compaction: " <> (if optEnableCompaction opts then "enabled" else "disabled")
     TextIO.hPutStrLn IO.stderr "  Prompt source: stdin (\\0-delimited)"
     Exit.exitSuccess
 
@@ -429,6 +437,7 @@ test =
         optMaxCost opts Test.@=? 0
         optMaxIter opts Test.@=? 0
         optProvider opts Test.@=? "auto"
+        optEnableCompaction opts Test.@=? False
     ]
 
 -- | Verbosity level for agent output
@@ -459,6 +468,7 @@ data AgentOptions = AgentOptions
     optMaxIterSet :: Bool,
     optCheckpointDir :: Maybe FilePath,
     optResume :: Maybe FilePath,
+    optEnableCompaction :: Bool,
     optPromptArg :: Maybe String,
     -- | Optional event callback for programmatic use (e.g., Telegram integration)
     optOnEvent :: Maybe (Trace.Event -> IO ())
@@ -486,6 +496,7 @@ defaultOptions =
       optMaxIterSet = False,
       optCheckpointDir = Nothing,
       optResume = Nothing,
+      optEnableCompaction = False,
       optPromptArg = Nothing,
       optOnEvent = Nothing
     }
@@ -937,6 +948,11 @@ runAgentWithState signalState opts initialState = do
                               (if optMaxTokens opts' > 0 then optMaxTokens opts' else maxBound)
                           )
                       else Nothing
+                  compactionCfg =
+                    Compaction.defaultCompactionConfig
+                      { Compaction.ccContextLimit = Models.getContextWindow modelName,
+                        Compaction.ccSummarizationModel = Op.Model modelName
+                      }
                   baseConfig =
                     OpAgent.defaultAgentConfig
                       { OpAgent.acModel = Op.Model modelName,
@@ -944,7 +960,8 @@ runAgentWithState signalState opts initialState = do
                         OpAgent.acMaxIterations = optMaxIter opts',
                         OpAgent.acBudget = budget,
                         OpAgent.acTools = workflowTools,
-                        OpAgent.acCheckpointInterval = if isJust (optCheckpointDir opts') then 5 else 0
+                        OpAgent.acCheckpointInterval = if isJust (optCheckpointDir opts') then 5 else 0,
+                        OpAgent.acCompaction = if optEnableCompaction opts' then Just compactionCfg else Nothing
                       }
                   (opConfig, seqToolsAllowed) = OpAgent.resolveAgentTools baseConfig seqTools
 
diff --git a/Omni/Agentd/Daemon.hs b/Omni/Agentd/Daemon.hs
index 28dc10b3..c17caa55 100644
--- a/Omni/Agentd/Daemon.hs
+++ b/Omni/Agentd/Daemon.hs
@@ -962,6 +962,7 @@ renderAgentExecScript agentCmd =
       "  --model \"${AGENTD_MODEL}\" \\",
       "  --run-id \"${AGENTD_AGENT_NAME}\" \\",
       "  --json \\",
+      "  --enable-compaction \\",
       "  --checkpoint-dir \"$CHECKPOINT_DIR\" \\",
       "  \"${RESUME_ARGS[@]}\" \\",
       "  \"${EXTRA_ARGS[@]}\" <&3"
@@ -2664,6 +2665,7 @@ test =
       Test.unit "persistent runtime script uses agent stdin mode" <| do
         let script = renderAgentExecScript "/nix/store/abc-agent/bin/agent"
         Test.assertBool "script should launch agent with json output" ("--json" `Text.isInfixOf` script)
+        Test.assertBool "script should enable compaction for persistent sessions" ("--enable-compaction" `Text.isInfixOf` script)
         Test.assertBool "script should forward run-id" ("--run-id \"${AGENTD_AGENT_NAME}\"" `Text.isInfixOf` script)
         Test.assertBool "script should append stdout to per-agent sessions jsonl" ("exec > >(tee -a \"$SESSION_FILE\")" `Text.isInfixOf` script)
         Test.assertBool "script should keep per-agent checkpoint dir" ("CHECKPOINT_DIR=\"${CHECKPOINTS_DIR}/${AGENTD_AGENT_NAME}\"" `Text.isInfixOf` script)
diff --git a/Omni/Agentd/SPEC.md b/Omni/Agentd/SPEC.md
index e82a3a8a..12b05614 100644
--- a/Omni/Agentd/SPEC.md
+++ b/Omni/Agentd/SPEC.md
@@ -113,6 +113,9 @@ when that file exists, so `agentd restart` restores prior conversation context.
 The agent executable path is baked into the generated runtime wrapper from agentd's bundled
 runtime dependency, so persistent sessions do not rely on `AGENTD_AGENT_COMMAND` or ambient PATH drift.
 
+Persistent wrappers also pass `--enable-compaction`, so long-lived agentd sessions compact history
+near context limits while standard direct `agent` runs remain no-compaction by default.
+
 Persistent runtime assets are generated by `agentd` itself on create/start:
 
 - `~/.config/systemd/user/agentd-agent@.service`