Rewrite Omni/Agent/Subagent/Coder.hs using Op primitives.
The Coder subagent has a well-defined structure: init -> work -> verify -> commit. This is a good candidate for expressing as an Op program with checkpoints.
module Omni.Agent.Programs.Coder where
import Omni.Agent.Op
import Omni.Agent.State.CRDT
-- | Coder workflow as an Op
coder :: CoderConfig -> Op CodeState CoderResult
coder config = do
emit (EventCustom "coder_start" (toJSON config))
-- Init phase
checkpoint "before-init"
initResult <- limit (initBudget config) (coderInit config)
case initResult of
Left exhausted -> do
emit (EventCustom "init_budget_exhausted" Null)
pure (CoderFailed "Init budget exhausted")
Right (Left err) -> do
emit (EventCustom "init_failed" (toJSON err))
pure (CoderFailed err)
Right (Right ctx) -> do
emit (EventCustom "init_success" (toJSON ctx))
put ctx
-- Work phase
checkpoint "before-work"
workResult <- limit (workBudget config) (coderWork config)
case workResult of
Left exhausted -> do
recovery config
pure (CoderFailed "Work budget exhausted")
Right (Left err) -> do
recovery config
pure (CoderFailed err)
Right (Right ()) -> do
-- Verify phase
checkpoint "before-verify"
verifyResult <- verifyWithRetry (verifyRetries config) config
case verifyResult of
Left err -> do
recovery config
pure (CoderFailed err)
Right () -> do
-- Commit phase
checkpoint "before-commit"
commit <- gitCommit config
emit (EventCustom "coder_complete" (toJSON commit))
pure (CoderSuccess commit)
-- | Init phase: check git status, verify build works
coderInit :: CoderConfig -> Op CodeState (Either Text CodeContext)
coderInit config = do
let ns = coderNamespace config
-- Check git status
gitStatus <- tool "bash" (toJSON ("git status --porcelain"))
when (hasUncommitted gitStatus) do
emit (EventCustom "warning" "Uncommitted changes in workspace")
-- Check if namespace builds
buildResult <- tool "bash" (toJSON ("bild " <> ns))
case buildResult of
BuildFailed err -> pure (Left ("Initial build failed: " <> err))
BuildOk -> do
branch <- tool "bash" (toJSON "git branch --show-current")
pure (Right CodeContext
{ ctxNamespace = ns
, ctxBranch = parseBranch branch
, ctxInitiallyBroken = False
})
-- | Work phase: agent loop to implement the task
coderWork :: CoderConfig -> Op CodeState (Either Text ())
coderWork config = do
-- Run the agent with coder-specific tools and prompt
let prompt = coderPrompt config
response <- infer (coderModel config) prompt
-- Process tool calls, iterate until done
workLoop config response 0
workLoop :: CoderConfig -> Response -> Int -> Op CodeState (Either Text ())
workLoop config response iteration
| iteration >= maxWorkIterations config = pure (Left "Max iterations exceeded")
| otherwise = do
case responseToolCalls response of
[] -> pure (Right ()) -- done
calls -> do
-- Execute tools
results <- traverse (executeCoderTool config) calls
-- Check for errors that need intervention
when (any isBlockingError results) do
checkpoint ("work-blocked-" <> tshow iteration)
-- Continue
let newPrompt = addToolResults response results
nextResponse <- infer (coderModel config) newPrompt
workLoop config nextResponse (iteration + 1)
-- | Verify phase with retries
verifyWithRetry :: Int -> CoderConfig -> Op CodeState (Either Text ())
verifyWithRetry 0 _ = pure (Left "Max verify retries exceeded")
verifyWithRetry n config = do
let ns = coderNamespace config
-- Run lint
lintResult <- timeout 60 (tool "bash" (toJSON ("lint --fix " <> ns)))
case lintResult of
Left TimedOut -> do
emit (EventCustom "lint_timeout" Null)
verifyWithRetry (n-1) config
Right lintOut | hasLintErrors lintOut -> do
-- Try to fix lint errors
fixResponse <- infer (coderModel config) (fixLintPrompt lintOut)
verifyWithRetry (n-1) config
Right _ -> do
-- Run build
buildResult <- timeout 120 (tool "bash" (toJSON ("bild " <> ns)))
case buildResult of
Left TimedOut -> verifyWithRetry (n-1) config
Right buildOut | hasBuildErrors buildOut -> do
fixResponse <- infer (coderModel config) (fixBuildPrompt buildOut)
verifyWithRetry (n-1) config
Right _ -> do
-- Run tests
testResult <- timeout 120 (tool "bash" (toJSON ("bild --test " <> ns)))
case testResult of
Left TimedOut -> verifyWithRetry (n-1) config
Right testOut | hasTestFailures testOut -> do
fixResponse <- infer (coderModel config) (fixTestPrompt testOut)
verifyWithRetry (n-1) config
Right _ -> pure (Right ()) -- all passed!
-- | Recovery: revert changes on failure
recovery :: CoderConfig -> Op CodeState ()
recovery config = do
emit (EventCustom "recovery_start" Null)
-- Git reset to clean state
_ <- tool "bash" (toJSON "git checkout -- .")
emit (EventCustom "recovery_complete" Null)
-- | Git commit
gitCommit :: CoderConfig -> Op CodeState Text
gitCommit config = do
let ns = coderNamespace config
task = coderTask config
-- Stage changes
_ <- tool "bash" (toJSON "git add -A")
-- Generate commit message
diff <- tool "bash" (toJSON "git diff --cached")
commitMsg <- infer (coderModel config) (commitMsgPrompt ns task diff)
-- Commit
_ <- tool "bash" (toJSON ("git commit -m '" <> escapeQuotes commitMsg <> "'"))
pure commitMsg
data CodeState = CodeState
{ csNamespace :: Namespace
, csBranch :: Text
, csFilesModified :: GSet FilePath
, csErrors :: [Text]
, csCost :: Double
, csTokens :: Int
}
deriving (Show, Eq, Generic)
instance Semigroup CodeState -- for parallel operations
instance CRDT CodeState
data CodeContext = CodeContext
{ ctxNamespace :: Namespace
, ctxBranch :: Text
, ctxInitiallyBroken :: Bool
}
deriving (Show, Eq, Generic)
data CoderResult
= CoderSuccess Text -- commit message
| CoderFailed Text -- error message
deriving (Show, Eq, Generic)
data CoderConfig = CoderConfig
{ coderNamespace :: Namespace
, coderTask :: Text
, coderModel :: Model
, coderWorkDir :: FilePath
, initBudget :: Budget
, workBudget :: Budget
, verifyRetries :: Int
, maxWorkIterations :: Int
}
deriving (Show, Eq, Generic)
defaultCoderConfig :: Namespace -> Text -> CoderConfig
In Omni/Agent/Subagent/Coder.hs, add option to use new implementation:
runCoderSubagent :: CoderConfig -> IO CoderResult
runCoderSubagent config = do
let program = coder config
result <- runSequential seqConfig initialState program
case result of
Left err -> pure (CoderFailed err)
Right (r, _, _) -> pure r