summaryrefslogtreecommitdiff
path: root/Omni/Agent/Worker.hs
diff options
context:
space:
mode:
Diffstat (limited to 'Omni/Agent/Worker.hs')
-rw-r--r--Omni/Agent/Worker.hs108
1 files changed, 64 insertions, 44 deletions
diff --git a/Omni/Agent/Worker.hs b/Omni/Agent/Worker.hs
index be971cf..511f309 100644
--- a/Omni/Agent/Worker.hs
+++ b/Omni/Agent/Worker.hs
@@ -11,9 +11,9 @@ import qualified Omni.Agent.Git as Git
import qualified Omni.Log as Log
import qualified Omni.Task.Core as TaskCore
import qualified System.Directory as Directory
-import qualified System.Process as Process
import qualified System.Exit as Exit
import System.FilePath ((</>))
+import qualified System.Process as Process
start :: Core.Worker -> IO ()
start worker = do
@@ -23,20 +23,20 @@ start worker = do
loop :: Core.Worker -> IO ()
loop worker = do
let repo = Core.workerPath worker
-
+
Log.info ["worker", "syncing tasks"]
-- Sync with live first to get latest code and tasks
-- We ignore errors here to keep the loop alive, but syncWithLive panics on conflict.
-- Ideally we should catch exceptions, but for now let it fail and restart (via supervisor or manual).
Git.syncWithLive repo
-
+
-- Sync tasks database (import from live)
-- Since we rebased, .tasks/tasks.jsonl should be up to date with live.
-- But we might need to consolidate if there are merge artifacts (not likely with rebase).
-- The bash script calls ./Omni/Agent/sync-tasks.sh which calls 'task import'.
-- Here we rely on 'task loadTasks' reading the file.
-- But 'syncWithLive' already updated the file from git.
-
+
-- Find ready work
readyTasks <- TaskCore.getReadyTasks
case readyTasks of
@@ -44,7 +44,6 @@ loop worker = do
Log.info ["worker", "no work found, sleeping"]
threadDelay (60 * 1000000) -- 60 seconds
loop worker
-
(task : _) -> do
processTask worker task
loop worker
@@ -53,87 +52,108 @@ processTask :: Core.Worker -> TaskCore.Task -> IO ()
processTask worker task = do
let repo = Core.workerPath worker
let tid = TaskCore.taskId task
-
+
Log.info ["worker", "claiming task", tid]
-
+
-- Claim task
TaskCore.updateTaskStatus tid TaskCore.InProgress
-
+
-- Commit claim locally
Git.commit repo ("task: claim " <> tid)
-
+
-- Prepare branch
let taskBranch = "task/" <> tid
currentBranch <- Git.getCurrentBranch repo
if currentBranch == taskBranch
then Log.info ["worker", "resuming branch", taskBranch]
else Git.createBranch repo taskBranch
-
+
-- Run Amp
exitCode <- runAmp repo task
-
+
case exitCode of
Exit.ExitSuccess -> do
Log.info ["worker", "agent finished successfully"]
-
+
+ -- Update status to Review (bundled with feature commit)
+ TaskCore.updateTaskStatus tid TaskCore.Review
+
-- Commit changes
-- We should check if there are changes, but 'git add .' is safe.
Git.commit repo ("feat: implement " <> tid)
-
+
-- Submit for review
Log.info ["worker", "submitting for review"]
-
+
-- Switch back to worker base
let base = Core.workerName worker
Git.checkout repo base
-
+
-- Sync again
Git.syncWithLive repo
-
- -- Update status to Review
+
+ -- Update status to Review (for signaling)
TaskCore.updateTaskStatus tid TaskCore.Review
Git.commit repo ("task: review " <> tid)
-
Exit.ExitFailure code -> do
Log.warn ["worker", "agent failed with code", Text.pack (show code)]
threadDelay (10 * 1000000) -- Sleep 10s
runAmp :: FilePath -> TaskCore.Task -> IO Exit.ExitCode
runAmp repo task = do
- let prompt = "You are a Worker Agent.\n"
- <> "Your goal is to implement the following task:\n\n"
- <> formatTask task
- <> "\n\nINSTRUCTIONS:\n"
- <> "1. Analyze the codebase (use finder/Grep) to understand where to make changes.\n"
- <> "2. Implement the changes by editing files.\n"
- <> "3. Run tests to verify your work (e.g., 'bild --test Omni/Namespace').\n"
- <> "4. Fix any errors found during testing.\n"
- <> "5. Do NOT update the task status or manage git branches (the system handles that).\n"
- <> "6. When finished and tested, exit.\n\n"
- <> "Context:\n"
- <> "- You are working in '" <> Text.pack repo <> "'.\n"
- <> "- The task is in namespace '" <> maybe "root" (\x -> x) (TaskCore.taskNamespace task) <> "'.\n"
-
+ let prompt =
+ "You are a Worker Agent.\n"
+ <> "Your goal is to implement the following task:\n\n"
+ <> formatTask task
+ <> "\n\nINSTRUCTIONS:\n"
+ <> "1. Analyze the codebase (use finder/Grep) to understand where to make changes.\n"
+ <> "2. Implement the changes by editing files.\n"
+ <> "3. Run tests to verify your work (e.g., 'bild --test Omni/Namespace').\n"
+ <> "4. Fix any errors found during testing.\n"
+ <> "5. Do NOT update the task status or manage git branches (the system handles that).\n"
+ <> "6. When finished and tested, exit.\n\n"
+ <> "Context:\n"
+ <> "- You are working in '"
+ <> Text.pack repo
+ <> "'.\n"
+ <> "- The task is in namespace '"
+ <> fromMaybe "root" (TaskCore.taskNamespace task)
+ <> "'.\n"
+
Directory.createDirectoryIfMissing True (repo </> "_/llm")
-
+
-- Assume amp is in PATH
let args = ["--log-level", "debug", "--log-file", "_/llm/amp.log", "--dangerously-allow-all", "-x", Text.unpack prompt]
-
+
let cp = (Process.proc "amp" args) {Process.cwd = Just repo}
(_, _, _, ph) <- Process.createProcess cp
Process.waitForProcess ph
formatTask :: TaskCore.Task -> Text
formatTask t =
- "Task: " <> TaskCore.taskId t <> "\n"
- <> "Title: " <> TaskCore.taskTitle t <> "\n"
- <> "Type: " <> Text.pack (show (TaskCore.taskType t)) <> "\n"
- <> "Status: " <> Text.pack (show (TaskCore.taskStatus t)) <> "\n"
- <> "Priority: " <> Text.pack (show (TaskCore.taskPriority t)) <> "\n"
- <> maybe "" (\p -> "Parent: " <> p <> "\n") (TaskCore.taskParent t)
- <> maybe "" (\ns -> "Namespace: " <> ns <> "\n") (TaskCore.taskNamespace t)
- <> "Created: " <> Text.pack (show (TaskCore.taskCreatedAt t)) <> "\n"
- <> "Updated: " <> Text.pack (show (TaskCore.taskUpdatedAt t)) <> "\n"
- <> (if null (TaskCore.taskDependencies t) then "" else "\nDependencies:\n" <> Text.unlines (map formatDep (TaskCore.taskDependencies t)))
+ "Task: "
+ <> TaskCore.taskId t
+ <> "\n"
+ <> "Title: "
+ <> TaskCore.taskTitle t
+ <> "\n"
+ <> "Type: "
+ <> Text.pack (show (TaskCore.taskType t))
+ <> "\n"
+ <> "Status: "
+ <> Text.pack (show (TaskCore.taskStatus t))
+ <> "\n"
+ <> "Priority: "
+ <> Text.pack (show (TaskCore.taskPriority t))
+ <> "\n"
+ <> maybe "" (\p -> "Parent: " <> p <> "\n") (TaskCore.taskParent t)
+ <> maybe "" (\ns -> "Namespace: " <> ns <> "\n") (TaskCore.taskNamespace t)
+ <> "Created: "
+ <> Text.pack (show (TaskCore.taskCreatedAt t))
+ <> "\n"
+ <> "Updated: "
+ <> Text.pack (show (TaskCore.taskUpdatedAt t))
+ <> "\n"
+ <> (if null (TaskCore.taskDependencies t) then "" else "\nDependencies:\n" <> Text.unlines (map formatDep (TaskCore.taskDependencies t)))
where
formatDep dep = " - " <> TaskCore.depId dep <> " [" <> Text.pack (show (TaskCore.depType dep)) <> "]"