← Back to task

Commit 1487d8c8

commit 1487d8c824a63b449af021f494f3ced37c947559
Author: Coder Agent <coder@agents.omni>
Date:   Mon Apr 20 14:38:23 2026

    feat(agentd): add persistent context reset command
    
    Add 'agentd reset <id>' to clear checkpoint resume state for
    
    persistent sessions (and optionally clear session logs) without
    
    deleting agent config. This provides a first-class way to start
    
    a fresh conversation when a long-lived agent gets stuck.
    
    Task-Id: t-815

diff --git a/Omni/Agentd.hs b/Omni/Agentd.hs
index 6e44484f..8fa0502e 100755
--- a/Omni/Agentd.hs
+++ b/Omni/Agentd.hs
@@ -147,6 +147,11 @@ data Command
       { restartRunId :: Text,
         restartMode :: TargetMode
       }
+  | Reset
+      { resetRunId :: Text,
+        resetClearLog :: Bool,
+        resetMode :: TargetMode
+      }
   | Send
       { sendRunId :: Text,
         sendMessage :: Text,
@@ -578,6 +583,18 @@ restartParser =
     </ Opt.strArgument (Opt.metavar "ID" <> Opt.help "Persistent session name")
     <*> targetModeOption
 
+-- | Parser for unified 'reset' command
+resetParser :: Opt.Parser Command
+resetParser =
+  Reset
+    <. Text.pack
+    </ Opt.strArgument (Opt.metavar "ID" <> Opt.help "Persistent session name")
+    <*> Opt.switch
+      ( Opt.long "clear-log"
+          <> Opt.help "Also clear persistent session JSONL log"
+      )
+    <*> targetModeOption
+
 -- | Parser for unified 'send' command
 sendParser :: Opt.Parser Command
 sendParser =
@@ -668,6 +685,7 @@ commandParser =
         <> Opt.command "send" (Opt.info sendParser (Opt.progDesc "Send input to a run or persistent agent"))
         <> Opt.command "stop" (Opt.info stopParser (Opt.progDesc "Stop a run or persistent agent"))
         <> Opt.command "restart" (Opt.info restartParser (Opt.progDesc "Restart a persistent session"))
+        <> Opt.command "reset" (Opt.info resetParser (Opt.progDesc "Reset persistent session context (checkpoint state)"))
         <> Opt.command "rm" (Opt.info rmParser (Opt.progDesc "Remove a run or persistent agent"))
         <> Opt.command "purge" (Opt.info purgeParser (Opt.progDesc "Purge a run or persistent agent"))
         <> Opt.command "replay" (Opt.info replayParser (Opt.progDesc "Replay events (all runs, or specific run-id)"))
@@ -2506,6 +2524,20 @@ main = do
           case result of
             Left err -> exitWithTextError err
             Right _ -> TextIO.putStrLn <| "Restarted " <> runId
+    Reset runId clearLog mode -> do
+      target <- resolveTargetMode mode runId
+      case target of
+        Left err -> exitWithTextError err
+        Right ResolvedOneshot -> exitWithTextError "Reset is only supported for persistent sessions"
+        Right ResolvedPersistent -> do
+          result <- Daemon.resetPersistentAgentContext Nothing runId clearLog
+          case result of
+            Left err -> exitWithTextError err
+            Right _ ->
+              TextIO.putStrLn
+                <| "Reset context for "
+                <> runId
+                <> (if clearLog then " (log cleared)" else "")
     Send runId message mode -> do
       target <- resolveTargetMode mode runId
       case target of
diff --git a/Omni/Agentd/Daemon.hs b/Omni/Agentd/Daemon.hs
index 8a3ffa55..9785819b 100644
--- a/Omni/Agentd/Daemon.hs
+++ b/Omni/Agentd/Daemon.hs
@@ -40,6 +40,7 @@ module Omni.Agentd.Daemon
     startPersistentAgent,
     stopPersistentAgent,
     restartPersistentAgent,
+    resetPersistentAgentContext,
     sendPersistentAgent,
     removePersistentAgent,
     purgePersistentAgent,
@@ -1503,6 +1504,54 @@ restartPersistentAgent mDbPath runId =
                 forM_ mPid <| updateAgentPid conn runId
                 Right </ hydratePersistentAgent conn cfg
 
+clearPersistentContextArtifacts :: Text -> Bool -> IO ()
+clearPersistentContextArtifacts runId clearLog = do
+  fifo <- agentFifoPath runId
+  fifoExists <- Dir.doesPathExist fifo
+  when fifoExists <| Dir.removePathForcibly fifo
+  checkpointsDir <- agentCheckpointDir runId
+  checkpointsExist <- Dir.doesPathExist checkpointsDir
+  when checkpointsExist <| Dir.removePathForcibly checkpointsDir
+  when clearLog <| do
+    sessionPath <- agentSessionLogPath runId
+    Dir.createDirectoryIfMissing True (takeDirectory sessionPath)
+    TextIO.writeFile sessionPath ""
+
+clearPersistentContextDb :: SQL.Connection -> Text -> IO ()
+clearPersistentContextDb conn runId = do
+  now <- tshow </ Time.getCurrentTime
+  SQL.withTransaction conn <| do
+    SQL.execute conn "DELETE FROM messages WHERE run_id = ?" (SQL.Only runId)
+    SQL.execute
+      conn
+      "UPDATE agents SET prompt = '', pid = NULL, status = 'stopped', started_at = NULL, completed_at = NULL, error = NULL, summary = NULL, cost_cents = NULL, updated_at = ? WHERE run_id = ?"
+      (now, runId)
+
+resetPersistentAgentContext :: Maybe FilePath -> Text -> Bool -> IO (Either Text PersistentAgent)
+resetPersistentAgentContext mDbPath runId clearLog = do
+  mAgent <- getPersistentAgent mDbPath runId
+  case mAgent of
+    Nothing -> pure (Left ("Agent not found: " <> runId))
+    Just pa -> do
+      let wasActive = paSystemd pa == Active
+      stopResult <- stopPersistentAgent mDbPath runId
+      case stopResult of
+        Left err -> pure (Left err)
+        Right _ -> do
+          cleanupResult <- try @SomeException <| clearPersistentContextArtifacts runId clearLog
+          case cleanupResult of
+            Left err -> pure (Left ("Failed to reset context files for " <> runId <> ": " <> tshow err))
+            Right () -> do
+              dbResult <- try @SomeException <| withAgentDb mDbPath (`clearPersistentContextDb` runId)
+              case dbResult of
+                Left err -> pure (Left ("Failed to reset context state for " <> runId <> ": " <> tshow err))
+                Right () ->
+                  if wasActive
+                    then startPersistentAgent mDbPath runId
+                    else do
+                      mRefreshed <- getPersistentAgent mDbPath runId
+                      pure <| maybe (Left ("Agent not found after reset: " <> runId)) Right mRefreshed
+
 writePromptToFifo :: Text -> Text -> IO (Either Text ())
 writePromptToFifo runId message = do
   fifo <- agentFifoPath runId
diff --git a/Omni/Agentd/SPEC.md b/Omni/Agentd/SPEC.md
index f33f15e9..2cb60ce6 100644
--- a/Omni/Agentd/SPEC.md
+++ b/Omni/Agentd/SPEC.md
@@ -117,6 +117,12 @@ Persistent sessions are controlled with standard Unix signals:
 
 Prompt delivery to persistent sessions uses stdin with NUL-delimited frames.
 
+To reset a persistent agent's conversation context without deleting its config, use:
+
+- `agentd reset <name> --mode persistent`
+
+This clears checkpoint resume state before restarting the session. Add `--clear-log` to also truncate the persistent session JSONL log.
+
 Persistent sessions also persist agent checkpoints under `$AGENTD_STATE_DIR/checkpoints/<agent>/`.
 The runtime wrapper always passes `--checkpoint-dir` and auto-adds `--resume .../session-latest.json`
 when that file exists, so `agentd restart` restores prior conversation context.