← Back to task

Commit c9281bce

commit c9281bcefd906a04ba515c7c16a31e08aa11d391
Author: Coder Agent <coder@agents.omni>
Date:   Wed Apr 15 09:18:29 2026

    feat(agentd): bundle agent runtime and drop AGENTD_AGENT_COMMAND
    
    Add local run dep on Omni/Agent.hs for agentd binaries.
    
    Bake resolved agent path into persistent runtime wrapper.
    
    Task-Id: t-794

diff --git a/Omni/Agentd.hs b/Omni/Agentd.hs
index 859cd0e7..cba9394b 100755
--- a/Omni/Agentd.hs
+++ b/Omni/Agentd.hs
@@ -15,6 +15,7 @@
 -- : dep yaml
 -- : dep brick
 -- : dep vty
+-- : run Omni/Agent.hs
 module Omni.Agentd
   ( main,
     test,
@@ -2518,13 +2519,14 @@ main = do
       workspaceRoot <- maybe Daemon.defaultWorkspaceRoot Dir.makeAbsolute mWorkspace
       defaultDb <- Daemon.defaultDaemonDbPath
       defaultLogs <- Daemon.defaultDaemonLogRoot
+      bundledAgentCmd <- Text.unpack </ Daemon.resolveBundledAgentCommand
       let config =
             Daemon.DaemonConfig
               { Daemon.dcPort = port,
                 Daemon.dcDbPath = fromMaybe defaultDb mDbPath,
                 Daemon.dcLogRoot = fromMaybe defaultLogs mLogRoot,
                 Daemon.dcWorkspace = workspaceRoot,
-                Daemon.dcAgentCmd = "agent"
+                Daemon.dcAgentCmd = bundledAgentCmd
               }
       Daemon.runDaemon config
     Notifyd -> Daemon.runNotifyDaemon
diff --git a/Omni/Agentd/Daemon.hs b/Omni/Agentd/Daemon.hs
index 4192cd30..28dc10b3 100644
--- a/Omni/Agentd/Daemon.hs
+++ b/Omni/Agentd/Daemon.hs
@@ -26,6 +26,7 @@
 -- : dep time
 -- : dep uuid
 -- : dep unix
+-- : run Omni/Agent.hs
 module Omni.Agentd.Daemon
   ( -- * Running the daemon
     runDaemon,
@@ -34,6 +35,7 @@ module Omni.Agentd.Daemon
     defaultDaemonDbPath,
     defaultDaemonLogRoot,
     defaultWorkspaceRoot,
+    resolveBundledAgentCommand,
 
     -- * API Types
     SpawnRequest (..),
@@ -914,8 +916,8 @@ resolveNotifydCommand = do
       mResolved <- Dir.findExecutable "agentd"
       pure (Text.pack (fromMaybe "agentd" mResolved))
 
-renderAgentExecScript :: Text
-renderAgentExecScript =
+renderAgentExecScript :: Text -> Text
+renderAgentExecScript agentCmd =
   Text.unlines
     [ "#!/usr/bin/env bash",
       "set -euo pipefail",
@@ -945,7 +947,6 @@ renderAgentExecScript =
       "exec 3<>\"$FIFO\"",
       "exec > >(tee -a \"$SESSION_FILE\")",
       "",
-      "AGENTD_AGENT_COMMAND=\"${AGENTD_AGENT_COMMAND:-agent}\"",
       "RESUME_ARGS=()",
       "if [[ -f \"$SESSION_CHECKPOINT\" ]]; then",
       "  RESUME_ARGS=(--resume \"$SESSION_CHECKPOINT\")",
@@ -956,7 +957,7 @@ renderAgentExecScript =
       "  EXTRA_ARGS=(${AGENTD_EXTRA_ARGS})",
       "fi",
       "",
-      "exec \"$AGENTD_AGENT_COMMAND\" \\",
+      "exec \"" <> agentCmd <> "\" \\",
       "  --provider \"${AGENTD_PROVIDER}\" \\",
       "  --model \"${AGENTD_MODEL}\" \\",
       "  --run-id \"${AGENTD_AGENT_NAME}\" \\",
@@ -1023,10 +1024,11 @@ ensurePersistentRuntimeAssets = do
   notifydUnitPath <- notifydSystemdUnitPath
   stateDir <- agentStateDir
   notifydCmd <- resolveNotifydCommand
+  agentCmd <- resolveBundledAgentCommand
   Dir.createDirectoryIfMissing True envDir
   Dir.createDirectoryIfMissing True stateDir
   Dir.createDirectoryIfMissing True (takeDirectory unitPath)
-  TextIO.writeFile execPath renderAgentExecScript
+  TextIO.writeFile execPath (renderAgentExecScript agentCmd)
   (chmodCode, _chmodOut, chmodErr) <- Process.readProcessWithExitCode "chmod" ["0755", execPath] ""
   when (chmodCode /= Exit.ExitSuccess) <| Exception.throwIO <| IOError.userError ("chmod failed for " <> execPath <> ": " <> chmodErr)
   TextIO.writeFile unitPath (renderAgentSystemdUnit envDir execPath)
@@ -1203,14 +1205,12 @@ queryMainPid name = do
     then pure (Read.readMaybe (Text.unpack (Text.strip (Text.pack out))))
     else pure Nothing
 
-resolvePersistentAgentCommand :: IO Text
-resolvePersistentAgentCommand = do
-  mOverride <- Env.lookupEnv "AGENTD_AGENT_COMMAND"
-  case mOverride of
-    Just cmd -> pure (Text.pack cmd)
-    Nothing -> do
-      mResolved <- Dir.findExecutable "agent"
-      pure (Text.pack (fromMaybe "agent" mResolved))
+resolveBundledAgentCommand :: IO Text
+resolveBundledAgentCommand = do
+  mResolved <- Dir.findExecutable "agent"
+  case mResolved of
+    Nothing -> Exception.throwIO <| IOError.userError "agent executable not found on PATH (expected bundled runtime dep)"
+    Just rawPath -> Text.pack </ Dir.canonicalizePath rawPath
 
 resolvePersistentStateDir :: IO Text
 resolvePersistentStateDir = Text.pack </ agentStateDir
@@ -1220,7 +1220,6 @@ writeAgentEnvFile cfg = do
   dir <- agentEnvDir
   Dir.createDirectoryIfMissing True dir
   path <- agentEnvPath (acName cfg)
-  agentCmd <- resolvePersistentAgentCommand
   stateDir <- resolvePersistentStateDir
   let baseLines =
         [ "AGENTD_PROVIDER=" <> sanitizeEnvValue (acProvider cfg),
@@ -1228,7 +1227,6 @@ writeAgentEnvFile cfg = do
           "AGENTD_CWD=" <> sanitizeEnvValue (Text.pack (acCwd cfg)),
           "AGENTD_THINKING=" <> sanitizeEnvValue (acThinking cfg),
           "AGENTD_EXTRA_ARGS=" <> maybe "" sanitizeEnvValue (acExtraArgs cfg),
-          "AGENTD_AGENT_COMMAND=" <> sanitizeEnvValue agentCmd,
           "AGENTD_STATE_DIR=" <> sanitizeEnvValue stateDir
         ]
       extraLines = map (\(k, v) -> k <> "=" <> sanitizeEnvValue v) (Map.toAscList (acExtraEnv cfg))
@@ -2664,7 +2662,7 @@ test =
             Test.assertBool "updated_at key should be present" (isJust (KeyMap.lookup "updated_at" obj))
           Just _ -> Test.assertFailure "Expected object JSON for AgentInfo",
       Test.unit "persistent runtime script uses agent stdin mode" <| do
-        let script = renderAgentExecScript
+        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 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)
@@ -2672,6 +2670,8 @@ test =
         Test.assertBool "script should persist checkpoints" ("--checkpoint-dir \"$CHECKPOINT_DIR\"" `Text.isInfixOf` script)
         Test.assertBool "script should look for latest session checkpoint" ("SESSION_CHECKPOINT=\"${CHECKPOINT_DIR}/session-latest.json\"" `Text.isInfixOf` script)
         Test.assertBool "script should wire resume args" ("RESUME_ARGS=(--resume \"$SESSION_CHECKPOINT\")" `Text.isInfixOf` script)
+        Test.assertBool "script should execute bundled agent path" ("exec \"/nix/store/abc-agent/bin/agent\" \\" `Text.isInfixOf` script)
+        Test.assertBool "script should not require AGENTD_AGENT_COMMAND env var" (not ("AGENTD_AGENT_COMMAND" `Text.isInfixOf` script))
         Test.assertBool "script should not reference legacy agentd-rpc" (not ("agentd-rpc" `Text.isInfixOf` script))
         Test.assertBool "script should not use rpc mode flag" (not ("--mode rpc" `Text.isInfixOf` script)),
       Test.unit "notify parser extracts completion events" <| do
diff --git a/Omni/Agentd/SPEC.md b/Omni/Agentd/SPEC.md
index 1f0ac9cf..e82a3a8a 100644
--- a/Omni/Agentd/SPEC.md
+++ b/Omni/Agentd/SPEC.md
@@ -110,6 +110,9 @@ Persistent sessions also persist agent checkpoints under `$AGENTD_STATE_DIR/chec
 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.
 
+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 runtime assets are generated by `agentd` itself on create/start:
 
 - `~/.config/systemd/user/agentd-agent@.service`
diff --git a/Omni/Ide/deploy-agentd.sh b/Omni/Ide/deploy-agentd.sh
index 15c8a8d0..2dd8a8ee 100755
--- a/Omni/Ide/deploy-agentd.sh
+++ b/Omni/Ide/deploy-agentd.sh
@@ -2,25 +2,22 @@
 # Deploy agentd via Omni deployer with hermetic store-path references.
 #
 # This script is the canonical, repo-tracked way to publish agentd service config.
-# It ensures both agentd and agent runtime paths are absolute Nix store paths.
+# agentd carries agent as a local runtime dependency via bild metadata.
 
 set -euo pipefail
 
 CODEROOT=${CODEROOT:?must set CODEROOT}
 cd "$CODEROOT"
 
-bild Omni/Agent.hs
 bild Omni/Agentd.hs
 bild Omni/Deploy/Manifest.hs
 
-agent_store=$(readlink "$CODEROOT/_/nix/Omni/Agent.hs")
 agentd_store=$(readlink "$CODEROOT/_/nix/Omni/Agentd.hs")
 manifest_store=$(readlink "$CODEROOT/_/nix/Omni/Deploy/Manifest.hs")
 revision=$(git rev-parse --short HEAD)
 
 service_json=$(jq -n \
   --arg store_path "$agentd_store" \
-  --arg agent_cmd "$agent_store/bin/agent" \
   --arg revision "$revision" \
   '{
     name: "agentd",
@@ -38,7 +35,6 @@ service_json=$(jq -n \
       LANG: "en_US.utf8",
       LC_ALL: "en_US.utf8",
       PATH: "/run/current-system/sw/bin:/usr/bin:/bin",
-      AGENTD_AGENT_COMMAND: $agent_cmd,
       AGENTD_STATE_DIR: "/var/lib/omni/agentd-agents"
     },
     envFile: null,