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.