commit bf5c33c563939d6db1182d3bf5a99024dbdb2ea7
Author: Ben Sima <ben@bensima.com>
Date: Thu Jan 1 09:17:30 2026
Remove SubagentRole system, keep only Coder (t-314)
Simplify the subagent system to only support Coder:
- Remove SubagentRole variants: WebCrawler, CodeReviewer, DataExtractor, Researcher, General, CustomRole
- Remove role-related functions: toolsForRole variants, modelForRole variants, roleDescription, systemPromptForRole, loadSystemPromptForRole
- Remove runGenericSubagent (only Coder uses hardened Coder module)
- Rename spawn_subagent tool to spawn_coder
- Update tool description to mention skills for non-coding tasks
- Remove unused imports (Provider, Prompts, WebSearch, WebReader, Tools, Python)
Other roles have been migrated to skills (t-313):
- web-research skill replaces WebCrawler
- code-review skill replaces CodeReviewer
- data-extraction skill replaces DataExtractor
- research skill replaces Researcher
~320 lines removed.
Task-Id: t-314
diff --git a/Omni/Agent/Subagent.hs b/Omni/Agent/Subagent.hs
index ed215a38..41b05be0 100644
--- a/Omni/Agent/Subagent.hs
+++ b/Omni/Agent/Subagent.hs
@@ -2,19 +2,21 @@
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE NoImplicitPrelude #-}
--- | Subagent system for spawning specialized agents.
+-- | Subagent system for spawning the Coder agent.
--
--- Enables the orchestrator (Ava) to delegate focused tasks to specialized
--- subagents that run with their own tool sets and resource limits.
+-- Enables the orchestrator (Ava) to delegate coding tasks to a specialized
+-- Coder subagent that runs with its own tool set and resource limits.
--
-- Key features:
--- - Role-based tool selection (WebCrawler, CodeReviewer, etc.)
+-- - Hardened Coder with init/verify/commit phases
-- - Per-subagent resource limits (timeout, cost, tokens)
-- - Structured result format with confidence scores
-- - No sub-subagent spawning (hierarchical control)
-- - Async execution with status polling
-- - Audit logging for all events
--
+-- For non-coding tasks (research, web crawling, etc.), use skills instead.
+--
-- : out omni-agent-subagent
-- : dep aeson
-- : dep async
@@ -71,7 +73,6 @@ module Omni.Agent.Subagent
SubagentApiKeys (..),
toolsForRole,
modelForRole,
- systemPromptForRole,
-- * Defaults
defaultSubagentConfig,
@@ -96,14 +97,8 @@ import qualified Data.UUID
import qualified Data.UUID.V4
import qualified Omni.Agent.AuditLog as AuditLog
import qualified Omni.Agent.Engine as Engine
-import qualified Omni.Agent.Prompts.Core as Prompts
-import qualified Omni.Agent.Provider as Provider
import qualified Omni.Agent.Subagent.Coder as Coder
import qualified Omni.Agent.Subagent.Jobs as Jobs
-import qualified Omni.Agent.Tools as Tools
-import qualified Omni.Agent.Tools.Python as Python
-import qualified Omni.Agent.Tools.WebReader as WebReader
-import qualified Omni.Agent.Tools.WebSearch as WebSearch
import qualified Omni.Test as Test
import System.IO.Unsafe (unsafePerformIO)
import Text.Printf (printf)
@@ -205,13 +200,12 @@ test =
Test.group
"Omni.Agent.Subagent"
[ Test.unit "SubagentRole JSON roundtrip" <| do
- let roles = [WebCrawler, CodeReviewer, DataExtractor, Researcher]
- forM_ roles <| \role ->
- case Aeson.decode (Aeson.encode role) of
- Nothing -> Test.assertFailure ("Failed to decode role: " <> show role)
- Just decoded -> decoded Test.@=? role,
+ let role = Coder
+ case Aeson.decode (Aeson.encode role) of
+ Nothing -> Test.assertFailure "Failed to decode role"
+ Just decoded -> decoded Test.@=? role,
Test.unit "SubagentConfig JSON roundtrip" <| do
- let cfg = defaultSubagentConfig WebCrawler "test task"
+ let cfg = defaultSubagentConfig Coder "test task"
case Aeson.decode (Aeson.encode cfg) of
Nothing -> Test.assertFailure "Failed to decode SubagentConfig"
Just decoded -> subagentTask decoded Test.@=? "test task",
@@ -241,24 +235,15 @@ test =
case Aeson.decode (Aeson.encode status) of
Nothing -> Test.assertFailure ("Failed to decode status: " <> show status)
Just decoded -> decoded Test.@=? status,
- Test.unit "toolsForRole WebCrawler has web tools" <| do
+ Test.unit "toolsForRole Coder has coder tools" <| do
let keys = SubagentApiKeys "test-openrouter-key" (Just "test-kagi-key")
- let tools = toolsForRole WebCrawler keys
- let names = map Engine.toolName tools
- ("web_search" `elem` names) Test.@=? True
- ("read_webpages" `elem` names) Test.@=? True,
- Test.unit "toolsForRole CodeReviewer has code tools" <| do
- let keys = SubagentApiKeys "test-openrouter-key" Nothing
- let tools = toolsForRole CodeReviewer keys
- let names = map Engine.toolName tools
- ("read_file" `elem` names) Test.@=? True
- ("search_codebase" `elem` names) Test.@=? True,
- Test.unit "modelForRole returns appropriate models" <| do
- modelForRole WebCrawler Test.@=? "anthropic/claude-3-haiku"
- modelForRole CodeReviewer Test.@=? "anthropic/claude-sonnet-4"
- modelForRole Researcher Test.@=? "anthropic/claude-sonnet-4",
+ let tools = toolsForRole Coder keys
+ -- Coder tools come from Coder module
+ (length tools > 0) Test.@=? True,
+ Test.unit "modelForRole returns appropriate model" <| do
+ modelForRole Coder Test.@=? "anthropic/claude-sonnet-4",
Test.unit "defaultSubagentConfig has sensible defaults" <| do
- let cfg = defaultSubagentConfig WebCrawler "task"
+ let cfg = defaultSubagentConfig Coder "task"
subagentTimeout cfg Test.@=? 600
subagentMaxCost cfg Test.@=? 100.0
subagentMaxTokens cfg Test.@=? 200000
@@ -266,11 +251,11 @@ test =
Test.unit "spawnSubagentTool has correct name" <| do
let keys = SubagentApiKeys "test-openrouter-key" (Just "test-kagi-key")
let tool = spawnSubagentTool keys
- Engine.toolName tool Test.@=? "spawn_subagent",
- Test.unit "spawn_subagent returns approval request when not confirmed" <| do
+ Engine.toolName tool Test.@=? "spawn_coder",
+ Test.unit "spawn_coder returns approval request when not confirmed" <| do
let keys = SubagentApiKeys "test-openrouter-key" (Just "test-kagi-key")
let tool = spawnSubagentTool keys
- let args = Aeson.object ["role" .= ("web_crawler" :: Text), "task" .= ("test task" :: Text)]
+ let args = Aeson.object ["task" .= ("test task" :: Text), "namespace" .= ("Omni/Test" :: Text)]
result <- Engine.toolExecute tool args
case result of
Aeson.Object obj -> do
@@ -278,7 +263,7 @@ test =
status Test.@=? Just (Aeson.String "awaiting_approval")
_ -> Test.assertFailure "Expected object response",
Test.unit "pending spawn create and lookup works" <| do
- let config = defaultSubagentConfig WebCrawler "test pending task"
+ let config = defaultSubagentConfig Coder "test pending task"
(pid, sid) <- createPendingSpawn config 12345 Nothing
when (Text.null pid) <| Test.assertFailure "pending ID should not be empty"
when (Text.null sid) <| Test.assertFailure "subagent ID should not be empty"
@@ -293,7 +278,7 @@ test =
afterRemove <- getPendingSpawn pid
afterRemove Test.@=? Nothing,
Test.unit "pending spawn registry is isolated" <| do
- let config = defaultSubagentConfig Researcher "isolated test"
+ let config = defaultSubagentConfig Coder "isolated test"
(pid1, _) <- createPendingSpawn config 111 Nothing
(pid2, _) <- createPendingSpawn config 222 Nothing
when (pid1 == pid2) <| Test.assertFailure "IDs should be different"
@@ -304,35 +289,20 @@ test =
removePendingSpawn pid2
]
+-- | Subagent role - currently only Coder is supported.
+-- Other roles (web_crawler, researcher, etc.) have been migrated to skills.
data SubagentRole
- = WebCrawler
- | CodeReviewer
- | DataExtractor
- | Researcher
- | Coder
- | General
- | CustomRole Text
+ = Coder
deriving (Show, Eq, Generic)
instance Aeson.ToJSON SubagentRole where
- toJSON WebCrawler = Aeson.String "web_crawler"
- toJSON CodeReviewer = Aeson.String "code_reviewer"
- toJSON DataExtractor = Aeson.String "data_extractor"
- toJSON Researcher = Aeson.String "researcher"
toJSON Coder = Aeson.String "coder"
- toJSON General = Aeson.String "general"
- toJSON (CustomRole name) = Aeson.String name
instance Aeson.FromJSON SubagentRole where
parseJSON = Aeson.withText "SubagentRole" parseRole
where
- parseRole "web_crawler" = pure WebCrawler
- parseRole "code_reviewer" = pure CodeReviewer
- parseRole "data_extractor" = pure DataExtractor
- parseRole "researcher" = pure Researcher
parseRole "coder" = pure Coder
- parseRole "general" = pure General
- parseRole name = pure (CustomRole name)
+ parseRole _ = empty
-- | Per-spawn guardrails that override engine defaults
data SpawnGuardrails = SpawnGuardrails
@@ -699,14 +669,9 @@ defaultSubagentConfig role task =
subagentWorkDir = Nothing
}
+-- | Model for a subagent role
modelForRole :: SubagentRole -> Text
-modelForRole WebCrawler = "anthropic/claude-3-haiku"
-modelForRole CodeReviewer = "anthropic/claude-sonnet-4"
-modelForRole DataExtractor = "anthropic/claude-3-haiku"
-modelForRole Researcher = "anthropic/claude-sonnet-4"
modelForRole Coder = "anthropic/claude-sonnet-4"
-modelForRole General = "anthropic/claude-sonnet-4"
-modelForRole (CustomRole _) = "anthropic/claude-sonnet-4"
data SubagentApiKeys = SubagentApiKeys
{ subagentOpenRouterKey :: Text,
@@ -714,122 +679,20 @@ data SubagentApiKeys = SubagentApiKeys
}
deriving (Show, Eq)
+-- | Tools for a subagent role
+-- Coder uses the hardened Coder module with init/verify/commit phases
toolsForRole :: SubagentRole -> SubagentApiKeys -> [Engine.Tool]
-toolsForRole WebCrawler keys =
- let webSearchTools = case subagentKagiKey keys of
- Just kagiKey -> [WebSearch.webSearchTool kagiKey]
- Nothing -> []
- in webSearchTools
- <> [ WebReader.webReaderTool (subagentOpenRouterKey keys),
- Tools.searchCodebaseTool
- ]
-toolsForRole CodeReviewer _keys =
- [ Tools.readFileTool,
- Tools.searchCodebaseTool,
- Tools.searchAndReadTool,
- Tools.runBashTool
- ]
-toolsForRole DataExtractor keys =
- [ WebReader.webReaderTool (subagentOpenRouterKey keys),
- Tools.readFileTool,
- Tools.searchCodebaseTool
- ]
-toolsForRole Researcher keys =
- let webSearchTools = case subagentKagiKey keys of
- Just kagiKey -> [WebSearch.webSearchTool kagiKey]
- Nothing -> []
- in webSearchTools
- <> [ WebReader.webReaderTool (subagentOpenRouterKey keys),
- Tools.readFileTool,
- Tools.searchCodebaseTool,
- Tools.searchAndReadTool
- ]
--- Coder uses the hardened Coder module, toolsForRole not used
toolsForRole Coder _keys = Coder.coderTools
--- General role: balanced tools for non-specialized tasks
-toolsForRole General _keys =
- [ Tools.readFileTool,
- Tools.writeFileTool,
- Tools.editFileTool,
- Tools.runBashTool,
- Python.pythonExecTool,
- Tools.searchCodebaseTool,
- Tools.searchAndReadTool
- ]
-toolsForRole (CustomRole _) keys = toolsForRole Researcher keys
-
--- | Load system prompt from template (required)
-loadSystemPromptForRole :: SubagentRole -> Text -> Maybe Text -> IO Text
-loadSystemPromptForRole role task maybeContext = do
- let ctx =
- Aeson.object
- [ "role_description" .= roleDescription role,
- "task" .= task,
- "context" .= maybeContext
- ]
- result <- Prompts.renderPrompt "subagents/generic/system" ctx
- case result of
- Right prompt -> pure prompt
- Left err -> panic <| "Failed to load subagent system prompt: " <> err
-
--- | Legacy fallback prompt (deprecated - use template instead)
-systemPromptForRole :: SubagentRole -> Text -> Maybe Text -> Text
-systemPromptForRole role task maybeContext =
- Text.unlines
- [ "You are a specialized " <> roleDescription role <> " subagent working on a focused task.",
- "",
- "## Your Task",
- task,
- "",
- maybe "" (\ctx -> "## Context from Orchestrator\n" <> ctx <> "\n") maybeContext,
- "## Guidelines",
- "1. Be EFFICIENT with context - extract only key facts, don't save full page contents",
- "2. Summarize findings as you go rather than accumulating raw data",
- "3. Limit web page reads to 3-5 most relevant sources",
- "4. Work iteratively: search → skim results → read best 2-3 → synthesize",
- "5. ALWAYS cite sources - every claim needs a URL",
- "6. Stop when you have sufficient information - don't over-research",
- "",
- "## Output Format",
- "Return findings as a list of structured insights:",
- "",
- "```json",
- "{",
- " \"summary\": \"Brief overall summary (1-2 sentences)\",",
- " \"confidence\": 0.85,",
- " \"findings\": [",
- " {",
- " \"claim\": \"The key insight or fact discovered\",",
- " \"source_url\": \"https://example.com/page\",",
- " \"quote\": \"Relevant excerpt supporting the claim\",",
- " \"source_name\": \"Example Site\"",
- " }",
- " ],",
- " \"caveats\": \"Any limitations or uncertainties\"",
- "}",
- "```"
- ]
-roleDescription :: SubagentRole -> Text
-roleDescription WebCrawler = "web research"
-roleDescription CodeReviewer = "code review"
-roleDescription DataExtractor = "data extraction"
-roleDescription Researcher = "research"
-roleDescription Coder = "coding"
-roleDescription General = "general-purpose"
-roleDescription (CustomRole name) = name
+
runSubagent :: SubagentApiKeys -> SubagentConfig -> IO SubagentResult
runSubagent keys config = runSubagentWithCallbacks keys config defaultCallbacks
runSubagentWithCallbacks :: SubagentApiKeys -> SubagentConfig -> SubagentCallbacks -> IO SubagentResult
-runSubagentWithCallbacks keys config callbacks = do
- let role = subagentRole config
-
+runSubagentWithCallbacks keys config callbacks =
-- Coder role uses the hardened Coder module with init/verify/commit phases
- case role of
- Coder -> runCoderSubagentWrapper keys config callbacks
- _ -> runGenericSubagent keys config callbacks
+ runCoderSubagentWrapper keys config callbacks
-- | Run Coder subagent using the hardened Coder module
runCoderSubagentWrapper :: SubagentApiKeys -> SubagentConfig -> SubagentCallbacks -> IO SubagentResult
@@ -954,134 +817,25 @@ runCoderSubagentWrapper keys config callbacks = do
onSubagentComplete callbacks finalResult
pure finalResult
--- | Run generic (non-Coder) subagent
-runGenericSubagent :: SubagentApiKeys -> SubagentConfig -> SubagentCallbacks -> IO SubagentResult
-runGenericSubagent keys config callbacks = do
- startTime <- Clock.getCurrentTime
-
- let role = subagentRole config
- let model = fromMaybe (modelForRole role) (subagentModel config)
- let tools = toolsForRole role keys
- systemPrompt <- loadSystemPromptForRole role (subagentTask config) (subagentContext config)
-
- onSubagentStart callbacks ("Starting " <> tshow role <> " subagent...")
-
- let provider = Provider.defaultOpenRouter (subagentOpenRouterKey keys) model
-
- let guardrails =
- Engine.Guardrails
- { Engine.guardrailMaxCostCents = subagentMaxCost config,
- Engine.guardrailMaxTokens = subagentMaxTokens config,
- Engine.guardrailMaxDuplicateToolCalls = 20,
- Engine.guardrailMaxTestFailures = 3,
- Engine.guardrailMaxEditFailures = 5
- }
-
- let agentConfig =
- Engine.AgentConfig
- { Engine.agentModel = model,
- Engine.agentTools = tools,
- Engine.agentSystemPrompt = systemPrompt,
- Engine.agentMaxIterations = subagentMaxIterations config,
- Engine.agentGuardrails = guardrails
- }
-
- let engineConfig =
- Engine.EngineConfig
- { Engine.engineLLM = Engine.defaultLLM,
- Engine.engineOnCost = \_ _ -> pure (),
- Engine.engineOnActivity = onSubagentActivity callbacks,
- Engine.engineOnToolCall = onSubagentToolCall callbacks,
- Engine.engineOnAssistant = \_ -> pure (),
- Engine.engineOnToolResult = \_ _ _ -> pure (),
- Engine.engineOnComplete = pure (),
- Engine.engineOnError = \_ -> pure (),
- Engine.engineOnGuardrail = \_ -> pure (),
- Engine.engineOnToolTrace = \_ _ _ _ -> pure Nothing
- }
-
- let timeoutMicros = subagentTimeout config * 1000000
-
- resultOrTimeout <-
- race
- (threadDelay timeoutMicros)
- (Engine.runAgentWithProvider engineConfig provider agentConfig (subagentTask config))
-
- endTime <- Clock.getCurrentTime
- let durationSecs = round (Clock.diffUTCTime endTime startTime)
-
- let result = case resultOrTimeout of
- Left () ->
- SubagentResult
- { subagentOutput = Aeson.object ["error" .= ("Timeout after " <> tshow (subagentTimeout config) <> " seconds" :: Text)],
- subagentSummary = "Subagent timed out",
- subagentConfidence = 0.0,
- subagentTokensUsed = 0,
- subagentCostCents = 0.0,
- subagentDuration = durationSecs,
- subagentIterations = 0,
- subagentStatus = SubagentTimeout
- }
- Right (Left err) ->
- let status = if "cost" `Text.isInfixOf` Text.toLower err then SubagentCostExceeded else SubagentError err
- in SubagentResult
- { subagentOutput = Aeson.object ["error" .= err],
- subagentSummary = "Subagent failed: " <> err,
- subagentConfidence = 0.0,
- subagentTokensUsed = 0,
- subagentCostCents = 0.0,
- subagentDuration = durationSecs,
- subagentIterations = 0,
- subagentStatus = status
- }
- Right (Right agentResult) ->
- SubagentResult
- { subagentOutput = Aeson.object ["response" .= Engine.resultFinalMessage agentResult],
- subagentSummary = truncateSummary (Engine.resultFinalMessage agentResult),
- subagentConfidence = 0.8,
- subagentTokensUsed = Engine.resultTotalTokens agentResult,
- subagentCostCents = Engine.resultTotalCost agentResult,
- subagentDuration = durationSecs,
- subagentIterations = Engine.resultIterations agentResult,
- subagentStatus = SubagentSuccess
- }
-
- onSubagentComplete callbacks result
- pure result
- where
- truncateSummary :: Text -> Text
- truncateSummary txt =
- let firstLine = Text.takeWhile (/= '\n') txt
- in if Text.length firstLine > 200
- then Text.take 197 firstLine <> "..."
- else firstLine
-
+-- | Tool to spawn a coder subagent for code changes.
+-- For other workflows (research, web crawling, etc.), use skills instead.
spawnSubagentTool :: SubagentApiKeys -> Engine.Tool
spawnSubagentTool keys =
Engine.Tool
- { Engine.toolName = "spawn_subagent",
+ { Engine.toolName = "spawn_coder",
Engine.toolDescription =
- "Spawn a specialized subagent for a focused task. "
+ "Spawn a Coder subagent for code changes. The Coder runs in a hardened loop with "
+ <> "init (understand codebase) → implement → verify (build/test) → commit phases. "
<> "IMPORTANT: First call with confirmed=false to get approval request, "
<> "then present the approval to the user. Only call with confirmed=true "
<> "after the user explicitly approves. "
- <> "Available roles: web_crawler (fast web research), code_reviewer (thorough code analysis), "
- <> "data_extractor (structured data extraction), researcher (general research), "
- <> "coder (hardened coding with init/verify/commit - requires namespace and context), "
- <> "general (balanced tools for non-specialized tasks), "
- <> "custom (use custom_role_name and specify tools).",
+ <> "For non-coding tasks (research, data extraction, etc.), use skills instead.",
Engine.toolJsonSchema =
Aeson.object
[ "type" .= ("object" :: Text),
"properties"
.= Aeson.object
- [ "role"
- .= Aeson.object
- [ "type" .= ("string" :: Text),
- "enum" .= (["web_crawler", "code_reviewer", "data_extractor", "researcher", "coder", "general", "custom"] :: [Text]),
- "description" .= ("Subagent role determining tools and model" :: Text)
- ],
- "task"
+ [ "task"
.= Aeson.object
[ "type" .= ("string" :: Text),
"description" .= ("The specific task for the subagent to accomplish" :: Text)
@@ -1101,32 +855,11 @@ spawnSubagentTool keys =
[ "type" .= ("integer" :: Text),
"description" .= ("Timeout in seconds (default: 600)" :: Text)
],
- "max_cost_cents"
- .= Aeson.object
- [ "type" .= ("number" :: Text),
- "description" .= ("Maximum cost in cents (default: 100)" :: Text)
- ],
"namespace"
.= Aeson.object
[ "type" .= ("string" :: Text),
"description" .= ("Code namespace like 'Omni/Agent/Subagent' (required for coder role)" :: Text)
],
- "custom_role_name"
- .= Aeson.object
- [ "type" .= ("string" :: Text),
- "description" .= ("Name for custom role (when role=custom)" :: Text)
- ],
- "tools"
- .= Aeson.object
- [ "type" .= ("array" :: Text),
- "items" .= Aeson.object ["type" .= ("string" :: Text)],
- "description" .= ("Override default tools with specific tool names" :: Text)
- ],
- "system_prompt"
- .= Aeson.object
- [ "type" .= ("string" :: Text),
- "description" .= ("Additional system prompt instructions for this subagent" :: Text)
- ],
"guardrails"
.= Aeson.object
[ "type" .= ("object" :: Text),
@@ -1186,13 +919,7 @@ formatApprovalRequest config =
<> costStr
<> "\n\nProceed? (yes/no)"
roleText = case subagentRole config of
- WebCrawler -> "WebCrawler"
- CodeReviewer -> "CodeReviewer"
- DataExtractor -> "DataExtractor"
- Researcher -> "Researcher"
Coder -> "Coder"
- General -> "General"
- CustomRole name -> name
estimatedTime :: Int
estimatedTime = subagentTimeout config `div` 60
costStr = Text.pack (printf "%.2f" (subagentMaxCost config / 100))
@@ -1302,76 +1029,37 @@ subagentTools keys = [spawnSubagentTool keys, checkSubagentTool]
type ApprovalCallback = Int -> Text -> Text -> Text -> Int -> Double -> IO ()
-- | Spawn subagent tool that requires external approval via callback
+-- | Coder spawning tool that requires Telegram button approval
spawnSubagentToolWithApproval :: SubagentApiKeys -> Int -> Maybe Int -> ApprovalCallback -> Engine.Tool
spawnSubagentToolWithApproval keys chatId threadId onApprovalNeeded =
Engine.Tool
- { Engine.toolName = "spawn_subagent",
+ { Engine.toolName = "spawn_coder",
Engine.toolDescription =
- "Request to spawn a specialized subagent for a focused task. "
+ "Request to spawn a Coder subagent for code changes. "
+ <> "The Coder runs in a hardened loop with init → implement → verify → commit phases. "
<> "The user will receive a confirmation button to approve. "
- <> "IMPORTANT: The subagent does NOT start until the user clicks Approve - "
+ <> "IMPORTANT: The coder does NOT start until the user clicks Approve - "
<> "do NOT say 'spawned' or 'started', say 'requested' or 'awaiting approval'. "
- <> "Available roles: web_crawler (fast web research), code_reviewer (thorough code analysis), "
- <> "data_extractor (structured data extraction), researcher (general research), "
- <> "coder (hardened coding with init/verify/commit - requires namespace and context), "
- <> "general (balanced tools for non-specialized tasks), "
- <> "custom (use custom_role_name and specify tools).",
+ <> "For non-coding tasks (research, data extraction, etc.), use skills instead.",
Engine.toolJsonSchema =
Aeson.object
[ "type" .= ("object" :: Text),
"properties"
.= Aeson.object
- [ "role"
- .= Aeson.object
- [ "type" .= ("string" :: Text),
- "enum" .= (["web_crawler", "code_reviewer", "data_extractor", "researcher", "coder", "general", "custom"] :: [Text]),
- "description" .= ("Subagent role determining tools and model" :: Text)
- ],
- "task"
+ [ "task"
.= Aeson.object
[ "type" .= ("string" :: Text),
- "description" .= ("The specific task for the subagent to accomplish" :: Text)
+ "description" .= ("The specific coding task to accomplish" :: Text)
],
"context"
.= Aeson.object
[ "type" .= ("string" :: Text),
- "description" .= ("Background context, related files, design decisions (required for coder)" :: Text)
- ],
- "model"
- .= Aeson.object
- [ "type" .= ("string" :: Text),
- "description" .= ("Override the default model for this role" :: Text)
- ],
- "timeout"
- .= Aeson.object
- [ "type" .= ("integer" :: Text),
- "description" .= ("Timeout in seconds (default: 600)" :: Text)
- ],
- "max_cost_cents"
- .= Aeson.object
- [ "type" .= ("number" :: Text),
- "description" .= ("Maximum cost in cents (default: 100)" :: Text)
+ "description" .= ("Background context, related files, design decisions - REQUIRED" :: Text)
],
"namespace"
.= Aeson.object
[ "type" .= ("string" :: Text),
- "description" .= ("Code namespace like 'Omni/Agent/Subagent' (required for coder role)" :: Text)
- ],
- "custom_role_name"
- .= Aeson.object
- [ "type" .= ("string" :: Text),
- "description" .= ("Name for custom role (when role=custom)" :: Text)
- ],
- "tools"
- .= Aeson.object
- [ "type" .= ("array" :: Text),
- "items" .= Aeson.object ["type" .= ("string" :: Text)],
- "description" .= ("Override default tools with specific tool names" :: Text)
- ],
- "system_prompt"
- .= Aeson.object
- [ "type" .= ("string" :: Text),
- "description" .= ("Additional system prompt instructions for this subagent" :: Text)
+ "description" .= ("Code namespace like 'Omni/Agent/Subagent' - REQUIRED" :: Text)
],
"guardrails"
.= Aeson.object
@@ -1398,13 +1086,7 @@ executeSpawnWithApproval _keys chatId threadId onApprovalNeeded v =
Aeson.Success config -> do
(pid, subagentId) <- createPendingSpawn config chatId threadId
let roleText = case subagentRole config of
- WebCrawler -> "web_crawler"
- CodeReviewer -> "code_reviewer"
- DataExtractor -> "data_extractor"
- Researcher -> "researcher"
Coder -> "coder"
- General -> "general"
- CustomRole name -> name
estimatedMins = subagentTimeout config `div` 60
maxCost = subagentMaxCost config
onApprovalNeeded chatId pid roleText (subagentTask config) estimatedMins maxCost