summaryrefslogtreecommitdiff
path: root/Omni/Agent
diff options
context:
space:
mode:
authorBen Sima <ben@bensima.com>2025-12-17 13:29:24 -0500
committerBen Sima <ben@bensima.com>2025-12-17 13:29:24 -0500
commit337648981cc5a55935116141341521f4fce83214 (patch)
treeaa5934ee9edd5413e16d76525b2e12efc0aec98c /Omni/Agent
parent91dff1309ceb0729bc3fdde61878f81fd3df4eec (diff)
Add Ava systemd deployment with dedicated user and workspace
- Add Omni.Agent.Paths module for configurable AVA_DATA_ROOT - Create ava Linux user in Users.nix with SSH key - Add systemd service in Beryllium/Ava.nix with graceful shutdown - Update Skills.hs and Outreach.hs to use configurable paths - Add startup logging of resolved paths in Telegram.hs - Create migration script for moving data from _/var/ava to /home/ava - Add deployment documentation in Beryllium/AVA.md In dev: AVA_DATA_ROOT unset uses _/var/ava/ In prod: AVA_DATA_ROOT=/home/ava via systemd Amp-Thread-ID: https://ampcode.com/threads/T-019b2d7e-bd88-7355-8133-275c65157aaf Co-authored-by: Amp <amp@ampcode.com>
Diffstat (limited to 'Omni/Agent')
-rw-r--r--Omni/Agent/Paths.hs39
-rw-r--r--Omni/Agent/Skills.hs5
-rw-r--r--Omni/Agent/Telegram.hs5
-rw-r--r--Omni/Agent/Tools/Outreach.hs16
4 files changed, 56 insertions, 9 deletions
diff --git a/Omni/Agent/Paths.hs b/Omni/Agent/Paths.hs
new file mode 100644
index 0000000..6facdc6
--- /dev/null
+++ b/Omni/Agent/Paths.hs
@@ -0,0 +1,39 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+-- | Configurable paths for Ava data directories.
+--
+-- In development, uses default paths under @_/var/ava/@.
+-- In production, set @AVA_DATA_ROOT@ to @/home/ava@ to use the dedicated workspace.
+module Omni.Agent.Paths
+ ( avaDataRoot,
+ skillsDir,
+ outreachDir,
+ userScratchRoot,
+ userScratchDir,
+ )
+where
+
+import Alpha
+import qualified Data.Text as Text
+import System.Environment (lookupEnv)
+import System.FilePath ((</>))
+import System.IO.Unsafe (unsafePerformIO)
+
+avaDataRoot :: FilePath
+avaDataRoot = unsafePerformIO <| do
+ m <- lookupEnv "AVA_DATA_ROOT"
+ pure (fromMaybe "_/var/ava" m)
+{-# NOINLINE avaDataRoot #-}
+
+skillsDir :: FilePath
+skillsDir = avaDataRoot </> "skills"
+
+outreachDir :: FilePath
+outreachDir = avaDataRoot </> "outreach"
+
+userScratchRoot :: FilePath
+userScratchRoot = avaDataRoot </> "users"
+
+userScratchDir :: Text -> FilePath
+userScratchDir user = userScratchRoot </> Text.unpack user
diff --git a/Omni/Agent/Skills.hs b/Omni/Agent/Skills.hs
index a9953b1..1dbf23f 100644
--- a/Omni/Agent/Skills.hs
+++ b/Omni/Agent/Skills.hs
@@ -42,6 +42,7 @@ import qualified Data.List as List
import qualified Data.Text as Text
import qualified Data.Text.IO as TextIO
import qualified Omni.Agent.Engine as Engine
+import qualified Omni.Agent.Paths as Paths
import qualified Omni.Test as Test
import qualified System.Directory as Directory
import qualified System.FilePath as FilePath
@@ -55,7 +56,7 @@ test =
"Omni.Agent.Skills"
[ Test.unit "skillsDir returns correct path" <| do
let dir = skillsDir
- ("_/var/ava/skills" `Text.isSuffixOf` Text.pack dir) Test.@=? True,
+ ("skills" `Text.isSuffixOf` Text.pack dir) Test.@=? True,
Test.unit "SkillMetadata parses from YAML frontmatter" <| do
let yaml = "name: test-skill\ndescription: A test skill"
case parseYamlFrontmatter yaml of
@@ -91,7 +92,7 @@ test =
-- | Base directory for all skills
skillsDir :: FilePath
-skillsDir = "_/var/ava/skills"
+skillsDir = Paths.skillsDir
-- | Skill metadata from YAML frontmatter
data SkillMetadata = SkillMetadata
diff --git a/Omni/Agent/Telegram.hs b/Omni/Agent/Telegram.hs
index e94e73d..fd6c6b5 100644
--- a/Omni/Agent/Telegram.hs
+++ b/Omni/Agent/Telegram.hs
@@ -81,6 +81,7 @@ import qualified Network.HTTP.Client as HTTPClient
import qualified Network.HTTP.Simple as HTTP
import qualified Omni.Agent.Engine as Engine
import qualified Omni.Agent.Memory as Memory
+import qualified Omni.Agent.Paths as Paths
import qualified Omni.Agent.Provider as Provider
import qualified Omni.Agent.Skills as Skills
import qualified Omni.Agent.Subagent as Subagent
@@ -1281,6 +1282,10 @@ startBot maybeToken = do
putText "Error: TELEGRAM_BOT_TOKEN not set and no --token provided"
exitFailure
+ putText <| "AVA data root: " <> Text.pack Paths.avaDataRoot
+ putText <| "Skills dir: " <> Text.pack Paths.skillsDir
+ putText <| "Outreach dir: " <> Text.pack Paths.outreachDir
+
ensureOllama
allowedIds <- loadAllowedUserIds
diff --git a/Omni/Agent/Tools/Outreach.hs b/Omni/Agent/Tools/Outreach.hs
index d601b36..e576cbd 100644
--- a/Omni/Agent/Tools/Outreach.hs
+++ b/Omni/Agent/Tools/Outreach.hs
@@ -60,8 +60,10 @@ import Data.Time (UTCTime, getCurrentTime)
import qualified Data.UUID as UUID
import qualified Data.UUID.V4 as UUID
import qualified Omni.Agent.Engine as Engine
+import qualified Omni.Agent.Paths as Paths
import qualified Omni.Test as Test
import qualified System.Directory as Directory
+import System.FilePath ((</>))
main :: IO ()
main = Test.run test
@@ -114,19 +116,19 @@ test =
]
outreachDir :: FilePath
-outreachDir = "_/var/ava/outreach"
+outreachDir = Paths.outreachDir
pendingDir :: FilePath
-pendingDir = outreachDir <> "/pending"
+pendingDir = outreachDir </> "pending"
approvedDir :: FilePath
-approvedDir = outreachDir <> "/approved"
+approvedDir = outreachDir </> "approved"
rejectedDir :: FilePath
-rejectedDir = outreachDir <> "/rejected"
+rejectedDir = outreachDir </> "rejected"
sentDir :: FilePath
-sentDir = outreachDir <> "/sent"
+sentDir = outreachDir </> "sent"
data OutreachType = Email | Message
deriving (Show, Eq, Generic)
@@ -210,7 +212,7 @@ ensureDirs = do
Directory.createDirectoryIfMissing True sentDir
draftPath :: FilePath -> Text -> FilePath
-draftPath dir draftId' = dir <> "/" <> Text.unpack draftId' <> ".json"
+draftPath dir draftId' = dir </> (Text.unpack draftId' <> ".json")
saveDraft :: OutreachDraft -> IO ()
saveDraft draft = do
@@ -254,7 +256,7 @@ listDrafts status = do
let jsonFiles = filter (".json" `isSuffixOf`) files
drafts <-
forM jsonFiles <| \f -> do
- content <- TextIO.readFile (dir <> "/" <> f)
+ content <- TextIO.readFile (dir </> f)
pure (Aeson.decode (BL.fromStrict (TE.encodeUtf8 content)))
pure (catMaybes drafts)