← Back to task

Commit d05ca473

commit d05ca4732710dd9cef7fffd998a03615ad2cb58c
Author: Ben Sima <ben@bensima.com>
Date:   Sun Nov 30 00:27:55 2025

    Add task complexity field and model selection
    
    All tests pass. Let me summarize the changes made:
    
    - Added `taskComplexity :: Maybe Int` field to the `Task` data type
    (1-5 - Updated SQL schema to include `complexity INTEGER` column -
    Updated `FromRow` and `ToRow` instances to handle the new field -
    Updated `tasksColumns` migration spec for automatic schema migration
    - Updated `saveTask` to include complexity in SQL INSERT - Updated
    `createTask` signature to accept `Maybe Int` for complexity
    
    - Added `--complexity=<c>` option to the docopt help string -
    Added complexity parsing in `create` command (validates 1-5 range)
    - Added complexity parsing in `edit` command - Updated `modifyFn`
    in edit to handle complexity updates - Updated all unit tests to
    use new `createTask` signature with complexi - Added CLI tests for
    `--complexity` flag parsing - Added unit tests for complexity field
    storage and persistence
    
    - Updated `selectModel` to use `selectModelByComplexity` based
    on task c - Added `selectModelByComplexity :: Maybe Int -> Text`
    function with map
      - `Nothing` or 3-4 → `anthropic/claude-sonnet-4-20250514` (default)
      - 1-2 → `anthropic/claude-haiku` (trivial/low complexity) - 5 →
      `anthropic/claude-opus-4-20250514` (expert complexity)
    
    - Updated `createTask` calls to include `Nothing` for complexity
    
    Task-Id: t-141.5

diff --git a/Omni/Agent/Worker.hs b/Omni/Agent/Worker.hs
index dafd0b25..aa7c5ab0 100644
--- a/Omni/Agent/Worker.hs
+++ b/Omni/Agent/Worker.hs
@@ -465,10 +465,20 @@ buildRetryPrompt (Just ctx) =
     <> "- If there were merge conflicts, the conflicting files may have been modified by others\n"
     <> "- Review the current state of those files before making changes\n"
 
--- | Select model based on task complexity
--- Currently always uses claude-sonnet-4, but can be extended for model selection
+-- | Select model based on task complexity (1-5 scale)
+-- Uses OpenRouter model identifiers for Claude models
 selectModel :: TaskCore.Task -> Text
-selectModel _ = "anthropic/claude-sonnet-4-20250514"
+selectModel task = selectModelByComplexity (TaskCore.taskComplexity task)
+
+-- | Select model based on complexity level
+selectModelByComplexity :: Maybe Int -> Text
+selectModelByComplexity Nothing = "anthropic/claude-sonnet-4-20250514"
+selectModelByComplexity (Just 1) = "anthropic/claude-haiku"
+selectModelByComplexity (Just 2) = "anthropic/claude-haiku"
+selectModelByComplexity (Just 3) = "anthropic/claude-sonnet-4-20250514"
+selectModelByComplexity (Just 4) = "anthropic/claude-sonnet-4-20250514"
+selectModelByComplexity (Just 5) = "anthropic/claude-opus-4-20250514"
+selectModelByComplexity (Just _) = "anthropic/claude-sonnet-4-20250514"
 
 formatTask :: TaskCore.Task -> Text
 formatTask t =
diff --git a/Omni/Task.hs b/Omni/Task.hs
index bf262ab9..c6e68ac9 100644
--- a/Omni/Task.hs
+++ b/Omni/Task.hs
@@ -83,6 +83,7 @@ Options:
   --type=<type>                 Task type: epic, task, or human (default: task)
   --parent=<id>                 Parent epic ID
   --priority=<p>                Priority: 0-4 (0=critical, 4=backlog, default: 2)
+  --complexity=<c>              Complexity: 1-5 for model selection (1=trivial, 5=expert)
   --status=<status>             Filter by status: draft, open, in-progress, review, approved, done
   --epic=<id>                   Filter stats by epic (recursive)
   --deps=<ids>                  Comma-separated list of dependency IDs
@@ -174,6 +175,13 @@ move' args
         Just "4" -> pure P4
         Just other -> panic <| "Invalid priority: " <> T.pack other <> ". Use: 0-4"
 
+      -- Parse complexity (1-5 scale)
+      complexity <- case Cli.getArg args (Cli.longOption "complexity") of
+        Nothing -> pure Nothing
+        Just c -> case readMaybe c of
+          Just n | n >= 1 && n <= 5 -> pure (Just n)
+          _ -> panic <| "Invalid complexity: " <> T.pack c <> ". Use: 1-5"
+
       namespace <- case Cli.getArg args (Cli.longOption "namespace") of
         Nothing -> pure Nothing
         Just ns -> do
@@ -186,7 +194,7 @@ move' args
         Nothing -> panic "--description is required for task create"
         Just d -> pure (T.pack d)
 
-      createdTask <- createTask title taskType parent namespace priority deps description
+      createdTask <- createTask title taskType parent namespace priority complexity deps description
       if isJsonMode args
         then outputJson createdTask
         else putStrLn <| "Created task: " <> T.unpack (taskId createdTask)
@@ -209,6 +217,11 @@ move' args
         Just "3" -> pure <| Just P3
         Just "4" -> pure <| Just P4
         Just other -> panic <| "Invalid priority: " <> T.pack other <> ". Use: 0-4"
+      maybeComplexity <- case Cli.getArg args (Cli.longOption "complexity") of
+        Nothing -> pure Nothing
+        Just c -> case readMaybe c of
+          Just n | n >= 1 && n <= 5 -> pure (Just (Just n))
+          _ -> panic <| "Invalid complexity: " <> T.pack c <> ". Use: 1-5"
       maybeStatus <- case Cli.getArg args (Cli.longOption "status") of
         Nothing -> pure Nothing
         Just "draft" -> pure <| Just Draft
@@ -248,6 +261,7 @@ move' args
                 taskNamespace = case maybeNamespace of Nothing -> taskNamespace task; Just ns -> Just ns,
                 taskStatus = fromMaybe (taskStatus task) maybeStatus,
                 taskPriority = fromMaybe (taskPriority task) maybePriority,
+                taskComplexity = fromMaybe (taskComplexity task) maybeComplexity,
                 taskDescription = fromMaybe (taskDescription task) maybeDesc,
                 taskDependencies = fromMaybe (taskDependencies task) maybeDeps
               }
@@ -449,60 +463,69 @@ unitTests =
         initTaskDb
         True Test.@?= True,
       Test.unit "can create task" <| do
-        task <- createTask "Test task" WorkTask Nothing Nothing P2 [] "Test description"
+        task <- createTask "Test task" WorkTask Nothing Nothing P2 Nothing [] "Test description"
         taskTitle task Test.@?= "Test task"
         taskType task Test.@?= WorkTask
         taskStatus task Test.@?= Open
         taskPriority task Test.@?= P2
         null (taskDependencies task) Test.@?= True,
       Test.unit "can create human task" <| do
-        task <- createTask "Human Task" HumanTask Nothing Nothing P2 [] "Human task description"
+        task <- createTask "Human Task" HumanTask Nothing Nothing P2 Nothing [] "Human task description"
         taskType task Test.@?= HumanTask,
       Test.unit "ready tasks exclude human tasks" <| do
-        task <- createTask "Human Task" HumanTask Nothing Nothing P2 [] "Human task"
+        task <- createTask "Human Task" HumanTask Nothing Nothing P2 Nothing [] "Human task"
         ready <- getReadyTasks
         (taskId task `notElem` map taskId ready) Test.@?= True,
       Test.unit "ready tasks exclude draft tasks" <| do
-        task <- createTask "Draft Task" WorkTask Nothing Nothing P2 [] "Draft description"
+        task <- createTask "Draft Task" WorkTask Nothing Nothing P2 Nothing [] "Draft description"
         updateTaskStatus (taskId task) Draft []
         ready <- getReadyTasks
         (taskId task `notElem` map taskId ready) Test.@?= True,
       Test.unit "can create task with description" <| do
-        task <- createTask "Test task" WorkTask Nothing Nothing P2 [] "My description"
+        task <- createTask "Test task" WorkTask Nothing Nothing P2 Nothing [] "My description"
         taskDescription task Test.@?= "My description",
+      Test.unit "can create task with complexity" <| do
+        task <- createTask "Complex task" WorkTask Nothing Nothing P2 (Just 4) [] "High complexity task"
+        taskComplexity task Test.@?= Just 4,
+      Test.unit "complexity is persisted" <| do
+        task <- createTask "Persisted complexity" WorkTask Nothing Nothing P2 (Just 3) [] "Medium complexity"
+        tasks <- loadTasks
+        case findTask (taskId task) tasks of
+          Nothing -> Test.assertFailure "Could not reload task"
+          Just reloaded -> taskComplexity reloaded Test.@?= Just 3,
       Test.unit "can list tasks" <| do
-        _ <- createTask "Test task for list" WorkTask Nothing Nothing P2 [] "List test"
+        _ <- createTask "Test task for list" WorkTask Nothing Nothing P2 Nothing [] "List test"
         tasks <- listTasks Nothing Nothing Nothing Nothing
         not (null tasks) Test.@?= True,
       Test.unit "ready tasks exclude blocked ones" <| do
-        task1 <- createTask "First task" WorkTask Nothing Nothing P2 [] "First description"
+        task1 <- createTask "First task" WorkTask Nothing Nothing P2 Nothing [] "First description"
         let blockingDep = Dependency {depId = taskId task1, depType = Blocks}
-        task2 <- createTask "Blocked task" WorkTask Nothing Nothing P2 [blockingDep] "Blocked description"
+        task2 <- createTask "Blocked task" WorkTask Nothing Nothing P2 Nothing [blockingDep] "Blocked description"
         ready <- getReadyTasks
         (taskId task1 `elem` map taskId ready) Test.@?= True
         (taskId task2 `notElem` map taskId ready) Test.@?= True,
       Test.unit "discovered-from dependencies don't block" <| do
-        task1 <- createTask "Original task" WorkTask Nothing Nothing P2 [] "Original"
+        task1 <- createTask "Original task" WorkTask Nothing Nothing P2 Nothing [] "Original"
         let discDep = Dependency {depId = taskId task1, depType = DiscoveredFrom}
-        task2 <- createTask "Discovered work" WorkTask Nothing Nothing P2 [discDep] "Discovered"
+        task2 <- createTask "Discovered work" WorkTask Nothing Nothing P2 Nothing [discDep] "Discovered"
         ready <- getReadyTasks
         -- Both should be ready since DiscoveredFrom doesn't block
         (taskId task1 `elem` map taskId ready) Test.@?= True
         (taskId task2 `elem` map taskId ready) Test.@?= True,
       Test.unit "related dependencies don't block" <| do
-        task1 <- createTask "Task A" WorkTask Nothing Nothing P2 [] "Task A description"
+        task1 <- createTask "Task A" WorkTask Nothing Nothing P2 Nothing [] "Task A description"
         let relDep = Dependency {depId = taskId task1, depType = Related}
-        task2 <- createTask "Task B" WorkTask Nothing Nothing P2 [relDep] "Task B description"
+        task2 <- createTask "Task B" WorkTask Nothing Nothing P2 Nothing [relDep] "Task B description"
         ready <- getReadyTasks
         -- Both should be ready since Related doesn't block
         (taskId task1 `elem` map taskId ready) Test.@?= True
         (taskId task2 `elem` map taskId ready) Test.@?= True,
       Test.unit "ready tasks exclude epics" <| do
-        epic <- createTask "Epic task" Epic Nothing Nothing P2 [] "Epic description"
+        epic <- createTask "Epic task" Epic Nothing Nothing P2 Nothing [] "Epic description"
         ready <- getReadyTasks
         (taskId epic `notElem` map taskId ready) Test.@?= True,
       Test.unit "ready tasks exclude tasks needing intervention (retry >= 3)" <| do
-        task <- createTask "Failing task" WorkTask Nothing Nothing P2 [] "Failing description"
+        task <- createTask "Failing task" WorkTask Nothing Nothing P2 Nothing [] "Failing description"
         ready1 <- getReadyTasks
         (taskId task `elem` map taskId ready1) Test.@?= True
         setRetryContext
@@ -517,26 +540,26 @@ unitTests =
         ready2 <- getReadyTasks
         (taskId task `notElem` map taskId ready2) Test.@?= True,
       Test.unit "child task gets sequential ID" <| do
-        parent <- createTask "Parent" Epic Nothing Nothing P2 [] "Parent epic"
-        child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 [] "Child 1 description"
-        child2 <- createTask "Child 2" WorkTask (Just (taskId parent)) Nothing P2 [] "Child 2 description"
+        parent <- createTask "Parent" Epic Nothing Nothing P2 Nothing [] "Parent epic"
+        child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 Nothing [] "Child 1 description"
+        child2 <- createTask "Child 2" WorkTask (Just (taskId parent)) Nothing P2 Nothing [] "Child 2 description"
         taskId child1 Test.@?= taskId parent <> ".1"
         taskId child2 Test.@?= taskId parent <> ".2",
       Test.unit "grandchild task gets sequential ID" <| do
-        parent <- createTask "Grandparent" Epic Nothing Nothing P2 [] "Grandparent epic"
-        child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 [] "Parent epic"
-        grandchild <- createTask "Grandchild" WorkTask (Just (taskId child)) Nothing P2 [] "Grandchild task"
+        parent <- createTask "Grandparent" Epic Nothing Nothing P2 Nothing [] "Grandparent epic"
+        child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 Nothing [] "Parent epic"
+        grandchild <- createTask "Grandchild" WorkTask (Just (taskId child)) Nothing P2 Nothing [] "Grandchild task"
         taskId grandchild Test.@?= taskId parent <> ".1.1",
       Test.unit "siblings of grandchild task get sequential ID" <| do
-        parent <- createTask "Grandparent" Epic Nothing Nothing P2 [] "Grandparent"
-        child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 [] "Parent"
-        grandchild1 <- createTask "Grandchild 1" WorkTask (Just (taskId child)) Nothing P2 [] "Grandchild 1"
-        grandchild2 <- createTask "Grandchild 2" WorkTask (Just (taskId child)) Nothing P2 [] "Grandchild 2"
+        parent <- createTask "Grandparent" Epic Nothing Nothing P2 Nothing [] "Grandparent"
+        child <- createTask "Parent" Epic (Just (taskId parent)) Nothing P2 Nothing [] "Parent"
+        grandchild1 <- createTask "Grandchild 1" WorkTask (Just (taskId child)) Nothing P2 Nothing [] "Grandchild 1"
+        grandchild2 <- createTask "Grandchild 2" WorkTask (Just (taskId child)) Nothing P2 Nothing [] "Grandchild 2"
         taskId grandchild1 Test.@?= taskId parent <> ".1.1"
         taskId grandchild2 Test.@?= taskId parent <> ".1.2",
       Test.unit "child ID generation skips gaps" <| do
-        parent <- createTask "Parent with gaps" Epic Nothing Nothing P2 [] "Parent with gaps"
-        child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 [] "Child 1"
+        parent <- createTask "Parent with gaps" Epic Nothing Nothing P2 Nothing [] "Parent with gaps"
+        child1 <- createTask "Child 1" WorkTask (Just (taskId parent)) Nothing P2 Nothing [] "Child 1"
         -- Manually create a task with .3 suffix to simulate a gap (or deleted task)
         let child3Id = taskId parent <> ".3"
             child3 =
@@ -548,6 +571,7 @@ unitTests =
                   taskNamespace = Nothing,
                   taskStatus = Open,
                   taskPriority = P2,
+                  taskComplexity = Nothing,
                   taskDependencies = [],
                   taskDescription = "Child 3",
                   taskComments = [],
@@ -557,10 +581,10 @@ unitTests =
         saveTask child3
 
         -- Create a new child, it should get .4, not .2
-        child4 <- createTask "Child 4" WorkTask (Just (taskId parent)) Nothing P2 [] "Child 4"
+        child4 <- createTask "Child 4" WorkTask (Just (taskId parent)) Nothing P2 Nothing [] "Child 4"
         taskId child4 Test.@?= taskId parent <> ".4",
       Test.unit "can edit task" <| do
-        task <- createTask "Original Title" WorkTask Nothing Nothing P2 [] "Original"
+        task <- createTask "Original Title" WorkTask Nothing Nothing P2 Nothing [] "Original"
         let modifyFn t = t {taskTitle = "New Title", taskPriority = P0}
         updated <- editTask (taskId task) modifyFn
         taskTitle updated Test.@?= "New Title"
@@ -573,7 +597,7 @@ unitTests =
             taskTitle reloaded Test.@?= "New Title"
             taskPriority reloaded Test.@?= P0,
       Test.unit "task lookup is case insensitive" <| do
-        task <- createTask "Case sensitive" WorkTask Nothing Nothing P2 [] "Case sensitive description"
+        task <- createTask "Case sensitive" WorkTask Nothing Nothing P2 Nothing [] "Case sensitive description"
         let tid = taskId task
             upperTid = T.toUpper tid
         tasks <- loadTasks
@@ -586,19 +610,19 @@ unitTests =
             validNs = Namespace.fromHaskellModule ns
         Namespace.toPath validNs Test.@?= "Omni/Task.hs",
       Test.unit "generated IDs are lowercase" <| do
-        task <- createTask "Lowercase check" WorkTask Nothing Nothing P2 [] "Lowercase description"
+        task <- createTask "Lowercase check" WorkTask Nothing Nothing P2 Nothing [] "Lowercase description"
         let tid = taskId task
         tid Test.@?= T.toLower tid
         -- check it matches regex for base36 (t-[0-9a-z]+)
         let isLowerBase36 = T.all (\c -> c `elem` ['0' .. '9'] ++ ['a' .. 'z'] || c == 't' || c == '-') tid
         isLowerBase36 Test.@?= True,
       Test.unit "dependencies are case insensitive" <| do
-        task1 <- createTask "Blocker" WorkTask Nothing Nothing P2 [] "Blocker description"
+        task1 <- createTask "Blocker" WorkTask Nothing Nothing P2 Nothing [] "Blocker description"
         let tid1 = taskId task1
             -- Use uppercase ID for dependency
             upperTid1 = T.toUpper tid1
             dep = Dependency {depId = upperTid1, depType = Blocks}
-        task2 <- createTask "Blocked" WorkTask Nothing Nothing P2 [dep] "Blocked description"
+        task2 <- createTask "Blocked" WorkTask Nothing Nothing P2 Nothing [dep] "Blocked description"
 
         -- task1 is Open, so task2 should NOT be ready
         ready <- getReadyTasks
@@ -612,7 +636,7 @@ unitTests =
       Test.unit "can create task with lowercase ID" <| do
         -- This verifies that lowercase IDs are accepted and not rejected
         let lowerId = "t-lowercase"
-        let task = Task lowerId "Lower" WorkTask Nothing Nothing Open P2 [] "Lower description" [] (read "2025-01-01 00:00:00 UTC") (read "2025-01-01 00:00:00 UTC")
+        let task = Task lowerId "Lower" WorkTask Nothing Nothing Open P2 Nothing [] "Lower description" [] (read "2025-01-01 00:00:00 UTC") (read "2025-01-01 00:00:00 UTC")
         saveTask task
         tasks <- loadTasks
         case findTask lowerId tasks of
@@ -620,7 +644,7 @@ unitTests =
           Nothing -> Test.assertFailure "Should find task with lowercase ID",
       Test.unit "generateId produces valid ID" <| do
         tid <- generateId
-        let task = Task tid "Auto" WorkTask Nothing Nothing Open P2 [] "Auto description" [] (read "2025-01-01 00:00:00 UTC") (read "2025-01-01 00:00:00 UTC")
+        let task = Task tid "Auto" WorkTask Nothing Nothing Open P2 Nothing [] "Auto description" [] (read "2025-01-01 00:00:00 UTC") (read "2025-01-01 00:00:00 UTC")
         saveTask task
         tasks <- loadTasks
         case findTask tid tasks of
@@ -644,7 +668,7 @@ unitTests =
       Test.unit "lowercase ID does not clash with existing uppercase ID" <| do
         -- Setup: Create task with Uppercase ID
         let upperId = "t-UPPER"
-        let task1 = Task upperId "Upper Task" WorkTask Nothing Nothing Open P2 [] "Upper description" [] (read "2025-01-01 00:00:00 UTC") (read "2025-01-01 00:00:00 UTC")
+        let task1 = Task upperId "Upper Task" WorkTask Nothing Nothing Open P2 Nothing [] "Upper description" [] (read "2025-01-01 00:00:00 UTC") (read "2025-01-01 00:00:00 UTC")
         saveTask task1
 
         -- Action: Try to create task with Lowercase ID (same letters)
@@ -655,7 +679,7 @@ unitTests =
         -- treating them as the same is dangerous.
 
         let lowerId = "t-upper"
-        let task2 = Task lowerId "Lower Task" WorkTask Nothing Nothing Open P2 [] "Lower description" [] (read "2025-01-01 00:00:01 UTC") (read "2025-01-01 00:00:01 UTC")
+        let task2 = Task lowerId "Lower Task" WorkTask Nothing Nothing Open P2 Nothing [] "Lower description" [] (read "2025-01-01 00:00:01 UTC") (read "2025-01-01 00:00:01 UTC")
         saveTask task2
 
         tasks <- loadTasks
@@ -697,14 +721,14 @@ unitTests =
         (parseQueryParam "InProgress" :: Either Text Status) Test.@?= Right InProgress
         (parseQueryParam "Done" :: Either Text Status) Test.@?= Right Done,
       Test.unit "can add comment to task" <| do
-        task <- createTask "Task with comment" WorkTask Nothing Nothing P2 [] "Description"
+        task <- createTask "Task with comment" WorkTask Nothing Nothing P2 Nothing [] "Description"
         updatedTask <- addComment (taskId task) "This is a test comment"
         length (taskComments updatedTask) Test.@?= 1
         case taskComments updatedTask of
           (c : _) -> commentText c Test.@?= "This is a test comment"
           [] -> Test.assertFailure "Expected at least one comment",
       Test.unit "can add multiple comments to task" <| do
-        task <- createTask "Task with comments" WorkTask Nothing Nothing P2 [] "Description"
+        task <- createTask "Task with comments" WorkTask Nothing Nothing P2 Nothing [] "Description"
         _ <- addComment (taskId task) "First comment"
         updatedTask <- addComment (taskId task) "Second comment"
         length (taskComments updatedTask) Test.@?= 2
@@ -714,7 +738,7 @@ unitTests =
             commentText c2 Test.@?= "Second comment"
           _ -> Test.assertFailure "Expected at least two comments",
       Test.unit "comments are persisted" <| do
-        task <- createTask "Persistent comments" WorkTask Nothing Nothing P2 [] "Description"
+        task <- createTask "Persistent comments" WorkTask Nothing Nothing P2 Nothing [] "Description"
         _ <- addComment (taskId task) "Persisted comment"
         tasks <- loadTasks
         case findTask (taskId task) tasks of
@@ -778,6 +802,20 @@ cliTests =
           Right args -> do
             args `Cli.has` Cli.command "create" Test.@?= True
             Cli.getArg args (Cli.longOption "priority") Test.@?= Just "1",
+      Test.unit "create with --complexity flag" <| do
+        let result = Docopt.parseArgs help ["create", "Test", "--complexity=3"]
+        case result of
+          Left err -> Test.assertFailure <| "Failed to parse 'create --complexity': " <> show err
+          Right args -> do
+            args `Cli.has` Cli.command "create" Test.@?= True
+            Cli.getArg args (Cli.longOption "complexity") Test.@?= Just "3",
+      Test.unit "edit with --complexity flag" <| do
+        let result = Docopt.parseArgs help ["edit", "t-abc123", "--complexity=4"]
+        case result of
+          Left err -> Test.assertFailure <| "Failed to parse 'edit --complexity': " <> show err
+          Right args -> do
+            args `Cli.has` Cli.command "edit" Test.@?= True
+            Cli.getArg args (Cli.longOption "complexity") Test.@?= Just "4",
       Test.unit "edit command" <| do
         let result = Docopt.parseArgs help ["edit", "t-abc123"]
         case result of
diff --git a/Omni/Task/Core.hs b/Omni/Task/Core.hs
index 07c74fc1..92936bb5 100644
--- a/Omni/Task/Core.hs
+++ b/Omni/Task/Core.hs
@@ -35,6 +35,7 @@ data Task = Task
     taskNamespace :: Maybe Text, -- Optional namespace (e.g., "Omni/Task", "Biz/Cloud")
     taskStatus :: Status,
     taskPriority :: Priority, -- Priority level (0-4)
+    taskComplexity :: Maybe Int, -- Complexity 1-5 for model selection
     taskDependencies :: [Dependency], -- List of dependencies with types
     taskDescription :: Text, -- Required description
     taskComments :: [Comment], -- Timestamped comments for extra context
@@ -292,6 +293,7 @@ instance SQL.FromRow Task where
       <*> SQL.field
       <*> SQL.field
       <*> SQL.field
+      <*> SQL.field -- complexity
       <*> SQL.field
       <*> (fromMaybe "" </ SQL.field) -- Handle NULL description from legacy data
       <*> SQL.field -- comments
@@ -307,6 +309,7 @@ instance SQL.ToRow Task where
       SQL.toField (taskNamespace t),
       SQL.toField (taskStatus t),
       SQL.toField (taskPriority t),
+      SQL.toField (taskComplexity t),
       SQL.toField (taskDependencies t),
       SQL.toField (taskDescription t),
       SQL.toField (taskComments t),
@@ -444,6 +447,7 @@ initTaskDb = do
       \ namespace TEXT, \
       \ status TEXT NOT NULL, \
       \ priority TEXT NOT NULL, \
+      \ complexity INTEGER, \
       \ dependencies TEXT NOT NULL, \
       \ description TEXT, \
       \ comments TEXT NOT NULL DEFAULT '[]', \
@@ -531,6 +535,7 @@ tasksColumns =
     ("namespace", "TEXT"),
     ("status", "TEXT"),
     ("priority", "TEXT"),
+    ("complexity", "INTEGER"),
     ("dependencies", "TEXT"),
     ("description", "TEXT"),
     ("comments", "TEXT"),
@@ -639,13 +644,13 @@ saveTask task =
     SQL.execute
       conn
       "INSERT OR REPLACE INTO tasks \
-      \ (id, title, type, parent, namespace, status, priority, dependencies, description, comments, created_at, updated_at) \
-      \ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
+      \ (id, title, type, parent, namespace, status, priority, complexity, dependencies, description, comments, created_at, updated_at) \
+      \ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
       task
 
 -- Create a new task
-createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> Priority -> [Dependency] -> Text -> IO Task
-createTask title taskType parent namespace priority deps description =
+createTask :: Text -> TaskType -> Maybe Text -> Maybe Text -> Priority -> Maybe Int -> [Dependency] -> Text -> IO Task
+createTask title taskType parent namespace priority complexity deps description =
   withTaskLock <| do
     let parent' = fmap normalizeId parent
         deps' = map normalizeDependency deps
@@ -665,6 +670,7 @@ createTask title taskType parent namespace priority deps description =
               taskNamespace = namespace,
               taskStatus = Open,
               taskPriority = priority,
+              taskComplexity = complexity,
               taskDependencies = deps',
               taskDescription = description,
               taskComments = [],
diff --git a/Omni/Task/RaceTest.hs b/Omni/Task/RaceTest.hs
index 78410a4e..8ab797a5 100644
--- a/Omni/Task/RaceTest.hs
+++ b/Omni/Task/RaceTest.hs
@@ -28,7 +28,7 @@ raceTest =
     initTaskDb
 
     -- Create a parent epic
-    parent <- createTask "Parent Epic" Epic Nothing Nothing P2 [] "Parent Epic description"
+    parent <- createTask "Parent Epic" Epic Nothing Nothing P2 Nothing [] "Parent Epic description"
     let parentId = taskId parent
 
     -- Create multiple children concurrently
@@ -39,7 +39,7 @@ raceTest =
     -- Run concurrent creations
     children <-
       mapConcurrently
-        (\i -> createTask ("Child " <> tshow i) WorkTask (Just parentId) Nothing P2 [] ("Child " <> tshow i <> " description"))
+        (\i -> createTask ("Child " <> tshow i) WorkTask (Just parentId) Nothing P2 Nothing [] ("Child " <> tshow i <> " description"))
         indices
 
     -- Check for duplicates in generated IDs