Migrate Coder Subagent to Op

t-369.13·WorkTask·
·
·
·Omni/Agent.hs
Parent:t-369·Created1 month ago·Updated1 month ago

Dependencies

Description

Edit

Rewrite Omni/Agent/Subagent/Coder.hs using Op primitives.

Context

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.

Deliverables

1. Create Omni/Agent/Programs/Coder.hs

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

2. Coder State

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)

3. Coder Config

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

4. Update Coder Subagent to Use Op

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

Notes

  • Explicit checkpoints at each phase boundary
  • Budget limits for each phase
  • Recovery on failure
  • Verify loop with retries
  • State tracks files modified for conflict detection

Testing

  • Init detects broken state
  • Work executes tools correctly
  • Verify retry loop works
  • Recovery cleans up on failure
  • Commit generates appropriate message
  • Checkpoints saved at phase boundaries
  • Can resume from any checkpoint

Files to Read First

  • Omni/Agent/Subagent/Coder.hs (current implementation)
  • Omni/Agent/Subagent/HARDENING.md (Anthropic patterns)
  • Omni/Agent/Op.hs
  • Omni/Agent/Interpreter/Sequential.hs

Timeline (2)

🔄[human]Open → InProgress1 month ago
🔄[human]InProgress → Done1 month ago