← Back to task

Commit 3343fc66

commit 3343fc667ec0e6e0743640e9828d4fb040ba0f7f
Author: Coder Agent <coder@agents.omni>
Date:   Thu Apr 9 15:50:11 2026

    agentd: remove steering-file mechanism and pi rpc remnants
    
    Remove steering-file control paths now that persistent sessions are
    stdin-framed and signal-driven.
    
    - Drop AGENT_STEERING_FILE polling from Omni/Agent
    - Remove oneshot steering-file writes from Omni/Agentd
    - Remove AGENT_STEERING_FILE container env wiring
    - Update daemon/docs comments away from pi --mode rpc wording
    - Update docs/spec/k8s notes from steering files to signal control
    
    Validated repo cleanup with:
    - rg "mode rpc"
    - rg "steering.file" -i
    
    Task-Id: t-759.4

diff --git a/Omni/Agent.hs b/Omni/Agent.hs
index 1efe1304..6a2ccfc9 100755
--- a/Omni/Agent.hs
+++ b/Omni/Agent.hs
@@ -62,7 +62,7 @@ import qualified Omni.Agent.Tools as Tools
 import qualified Omni.Agent.Trace as Trace
 import qualified Omni.Cli as Cli
 import qualified Omni.Test as Test
-import System.Directory (createDirectoryIfMissing, doesFileExist, getCurrentDirectory, removeFile)
+import System.Directory (createDirectoryIfMissing, doesFileExist, getCurrentDirectory)
 import qualified System.Environment as Environment
 import qualified System.Exit as Exit
 import System.FilePath (isAbsolute, takeDirectory, (</>))
@@ -844,8 +844,6 @@ runAgentWithState signalState opts initialState = do
                         TimeFormat.defaultTimeLocale
                         "%A, %B %d, %Y at %I:%M:%S %p %Z"
                         (Time.ZonedTime localTime tz)
-              mSteeringPath <- resolveSteeringPath opts' cwd
-
               -- Load AGENTS.md from cwd and parent directories (like pi does)
               contextFiles <- loadProjectContextFiles cwd
 
@@ -986,7 +984,7 @@ runAgentWithState signalState opts initialState = do
                     cancelRequested <- consumeSignalFlag (ssSigIntRequested signalState)
                     if cancelRequested
                       then pure (Just Op.SteeringCancel)
-                      else checkSteeringFile mSteeringPath
+                      else pure Nothing
 
                   onCheckpoint :: Text -> OpAgent.AgentState -> Trace.Trace -> IO (Either Text ())
                   onCheckpoint cpName' cpState' cpTrace' =
@@ -1064,36 +1062,6 @@ lookupEventMessage (Aeson.Object obj) =
     _ -> Nothing
 lookupEventMessage _ = Nothing
 
-resolveSteeringPath :: AgentOptions -> FilePath -> IO (Maybe FilePath)
-resolveSteeringPath opts cwd = do
-  mPath <- Environment.lookupEnv "AGENT_STEERING_FILE"
-  case mPath of
-    Just path -> pure (Just path)
-    Nothing -> do
-      mRunIdEnv <- Environment.lookupEnv "AGENT_RUN_ID"
-      let mRunId = optRunId opts <|> (Text.pack </ mRunIdEnv)
-      pure <| (\runId -> cwd </> "events" </> Text.unpack runId <> ".steering") </ mRunId
-
-checkSteeringFile :: Maybe FilePath -> IO (Maybe Op.Steering)
-checkSteeringFile mPath =
-  case mPath of
-    Nothing -> pure Nothing
-    Just path -> do
-      exists <- doesFileExist path
-      if not exists
-        then pure Nothing
-        else do
-          readResult <- Exception.try (TextIO.readFile path) :: IO (Either Exception.IOException Text)
-          case readResult of
-            Left _ -> pure Nothing
-            Right content -> do
-              let trimmed = Text.strip content
-              if Text.null trimmed
-                then pure Nothing
-                else do
-                  _ <- Exception.try (removeFile path) :: IO (Either Exception.IOException ())
-                  pure <| Just <| Op.SteeringMessage trimmed
-
 -- | Resolve provider from name and optional model
 resolveProvider :: Text -> Maybe Text -> IO (Either Text Provider.Provider)
 resolveProvider providerName mModel = do
diff --git a/Omni/Agent/README.md b/Omni/Agent/README.md
index 62e6883b..e5b544b0 100644
--- a/Omni/Agent/README.md
+++ b/Omni/Agent/README.md
@@ -42,15 +42,13 @@ Then select the provider/model explicitly:
 agent --provider=openai-codex --model=gpt-5.2-codex "fix the build"
 ```
 
-## Steering
+## Interactive Control
 
-Send guidance to a running agent between iterations:
+In stdin prompt mode (`agent` with no prompt arg):
 
-```bash
-agentd steer <run-id> "try a different approach"
-```
-
-The agent reads steering from `AGENT_STEERING_FILE` (defaults to `./events/<run-id>.steering` when `AGENT_RUN_ID` is set) and emits a `steering` event in the trace.
+- send prompts as UTF-8 chunks terminated by `\0`
+- send `SIGINT` to cancel the current turn
+- send `SIGTERM` to finish the current turn and exit cleanly
 
 ## Shutdown Handling
 
diff --git a/Omni/Agentd.hs b/Omni/Agentd.hs
index fa9e46f2..cbc7b577 100755
--- a/Omni/Agentd.hs
+++ b/Omni/Agentd.hs
@@ -212,10 +212,6 @@ runDir root runId = root </> Text.unpack runId
 eventsFile :: FilePath -> Text -> FilePath
 eventsFile root runId = runDir root runId </> "events.jsonl"
 
--- | Steering file for a run: /var/log/agentd/<run-id>/steering
-steeringFile :: FilePath -> Text -> FilePath
-steeringFile root runId = runDir root runId </> "steering"
-
 -- | Run mode file for a run: /var/log/agentd/<run-id>/mode
 runModeFile :: FilePath -> Text -> FilePath
 runModeFile root runId = runDir root runId </> "mode"
@@ -239,15 +235,6 @@ readRunMode logRoot runId = do
       pure <| Just (Text.strip content)
     else pure Nothing
 
--- | Write a steering message to a run's steering file
-writeSteeringMessage :: FilePath -> Text -> Text -> IO FilePath
-writeSteeringMessage logRoot runId message = do
-  let dir = runDir logRoot runId
-      path = steeringFile logRoot runId
-  Dir.createDirectoryIfMissing True dir
-  TextIO.appendFile path (message <> "\n")
-  pure path
-
 startModeText :: StartMode -> String
 startModeText = \case
   StartOneshot -> "oneshot"
@@ -1216,10 +1203,6 @@ runAgent prompt mName foreground verbose maxCost maxIter timeoutSecs mProvider m
     Just name -> pure name
     Nothing -> Events.runIdToText </ Events.newRunId
 
-  let workspaceEventsDir = cwd </> "events"
-  Dir.createDirectoryIfMissing True workspaceEventsDir
-  let containerSteeringFile = "/workspace/events/" <> Text.unpack runId <> ".steering"
-
   -- Get current user/group for file ownership
   uid <- Posix.getRealUserID
   gid <- Posix.getRealGroupID
@@ -1311,9 +1294,7 @@ runAgent prompt mName foreground verbose maxCost maxIter timeoutSecs mProvider m
                "KAGI_API_KEY",
                -- Pass run ID so agent can use it
                "-e",
-               "AGENT_RUN_ID=" <> Text.unpack runId,
-               "-e",
-               "AGENT_STEERING_FILE=" <> containerSteeringFile
+               "AGENT_RUN_ID=" <> Text.unpack runId
              ]
           ++ extraEnv
           ++ [
@@ -2306,14 +2287,8 @@ showPersistentStatusById runId asJson = do
         else printPersistentAgentDetail pa
 
 sendOneshotInput :: Text -> Text -> IO ()
-sendOneshotInput runId message = do
-  let trimmed = Text.strip message
-  when (Text.null trimmed) <| do
-    TextIO.hPutStrLn stderr "Error: steering message is empty"
-    Exit.exitWith (Exit.ExitFailure 1)
-  logRoot <- getLogRoot
-  path <- writeSteeringMessage logRoot runId trimmed
-  TextIO.putStrLn <| "Steering queued: " <> Text.pack path
+sendOneshotInput _runId _message =
+  exitWithTextError "Oneshot input injection is no longer supported. Use persistent sessions and 'agentd send'."
 
 removeOneshotRun :: Text -> IO (Either Text ())
 removeOneshotRun runId = do
diff --git a/Omni/Agentd/Daemon.hs b/Omni/Agentd/Daemon.hs
index c3bdfa2a..ac3f0ccd 100644
--- a/Omni/Agentd/Daemon.hs
+++ b/Omni/Agentd/Daemon.hs
@@ -6,12 +6,12 @@
 {-# LANGUAGE NoImplicitPrelude #-}
 {-# OPTIONS_GHC -Wno-unused-top-binds #-}
 
--- | Agentd Daemon - HTTP API for agent lifecycle management with native Pi adapter
+-- | Agentd Daemon - HTTP API for agent lifecycle management.
 --
 -- Provides a long-running daemon process with:
--- - HTTP API for spawning/managing pi agents
+-- - HTTP API for spawning/managing agents
 -- - SQLite persistence for agent state
--- - Direct process management of pi --mode rpc
+-- - Direct process management of agent stdin-mode processes
 -- - Webhook notifications on agent completion
 --
 -- : out agentd-daemon
@@ -433,7 +433,7 @@ instance ToJSON PersistentAgent where
 -- * Servant API
 
 type AgentAPI =
-  -- POST /agents - create + start a persistent pi agent
+  -- POST /agents - create + start a persistent agent
   "agents" :> ReqBody '[JSON] SpawnRequest :> Post '[JSON] SpawnResponse
     -- GET /agents - list all agents
     :<|> "agents" :> Get '[JSON] [AgentInfo]
@@ -455,7 +455,7 @@ agentAPI = Proxy
 
 -- * Server State
 
--- | Running pi agent process info
+-- | Running agent process info
 data RunningAgent = RunningAgent
   { raProcessHandle :: Process.ProcessHandle,
     raStdinHandle :: IO.Handle,
@@ -1301,7 +1301,7 @@ streamAgentLogs runId follow = do
 
 -- * Pi Agent Management (Native)
 
--- | Default model for pi agents (already in pi-compatible format)
+-- | Default model for agents.
 defaultModel :: Text
 defaultModel = Models.defaultModel
 
diff --git a/Omni/Agentd/SPEC.md b/Omni/Agentd/SPEC.md
index 505fe433..6eddc021 100644
--- a/Omni/Agentd/SPEC.md
+++ b/Omni/Agentd/SPEC.md
@@ -88,7 +88,6 @@ The container inherits these from the host:
 Agentd also sets:
 
 - `AGENT_RUN_ID`
-- `AGENT_STEERING_FILE`
 - `PATH=/repo/_/bin:/bin`
 - `CODEROOT=/repo`
 - `GIT_CONFIG_COUNT=1`
@@ -98,15 +97,14 @@ Agentd also sets:
 
 OAuth tokens are accessed via the mounted data directory (`~/.local/share/agent/auth.json`).
 
-## Steering
+## Runtime Control
 
-Send guidance to a running agent between iterations:
+Persistent sessions are controlled with standard Unix signals:
 
-```bash
-agentd steer <run-id> "try a different approach"
-```
+- `SIGINT` cancels the current turn
+- `SIGTERM` lets the current turn finish, then exits
 
-This writes to `<workspace>/events/<run-id>.steering` (or legacy `<workspace>/_/events/<run-id>.steering`). The agent reads `AGENT_STEERING_FILE`, injects the message, and emits a `steering` event in the trace.
+Prompt delivery to persistent sessions uses stdin with NUL-delimited frames.
 
 ## Examples
 
diff --git a/k8s/agents/README.md b/k8s/agents/README.md
index a9ca0364..76d4ab5e 100644
--- a/k8s/agents/README.md
+++ b/k8s/agents/README.md
@@ -62,14 +62,10 @@ kubectl create job agent-$(date +%s) \
   -- agent --json "fix the bug"
 ```
 
-## Steering
+## Runtime control
 
-To steer a running job, append to the steering file inside the pod:
-
-```bash
-kubectl exec -n agents job/agent-123 -- \
-  sh -c 'echo "try a different approach" >> /workspace/events/agent-123.steering'
-```
+To cancel the current turn for a running job, send SIGINT to the agent process.
+For graceful shutdown, send SIGTERM.
 
 ## Cleanup
 
diff --git a/k8s/agents/job-template.yaml b/k8s/agents/job-template.yaml
index aec1b023..91ad1475 100644
--- a/k8s/agents/job-template.yaml
+++ b/k8s/agents/job-template.yaml
@@ -41,8 +41,6 @@ spec:
               valueFrom:
                 fieldRef:
                   fieldPath: metadata.name
-            - name: AGENT_STEERING_FILE
-              value: /workspace/events/$(AGENT_RUN_ID).steering
           resources:
             requests:
               cpu: "500m"