diff options
| author | Ben Sima <ben@bensima.com> | 2025-12-17 13:29:24 -0500 |
|---|---|---|
| committer | Ben Sima <ben@bensima.com> | 2025-12-17 13:29:24 -0500 |
| commit | 337648981cc5a55935116141341521f4fce83214 (patch) | |
| tree | aa5934ee9edd5413e16d76525b2e12efc0aec98c /Omni/Agent | |
| parent | 91dff1309ceb0729bc3fdde61878f81fd3df4eec (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.hs | 39 | ||||
| -rw-r--r-- | Omni/Agent/Skills.hs | 5 | ||||
| -rw-r--r-- | Omni/Agent/Telegram.hs | 5 | ||||
| -rw-r--r-- | Omni/Agent/Tools/Outreach.hs | 16 |
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) |
