From 9fa7697cd979eaa15a2479819463c3bdd86cc99a Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Sun, 30 Nov 2025 21:30:00 -0500 Subject: Add agent observability: event logging and storage - Add Omni/Agent/Event.hs with AgentEvent types - Add agent_events table schema and CRUD functions to Core.hs - Add new callbacks to Engine.hs: onAssistant, onToolResult, onComplete, onError - Wire event logging into Worker.hs with session tracking Events are now persisted to SQLite for each agent work session, enabling visibility into agent reasoning and tool usage. Task-Id: t-197.1 Task-Id: t-197.2 Task-Id: t-197.3 --- Omni/Agent/Engine.hs | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) (limited to 'Omni/Agent/Engine.hs') diff --git a/Omni/Agent/Engine.hs b/Omni/Agent/Engine.hs index e019341..1f5dcc8 100644 --- a/Omni/Agent/Engine.hs +++ b/Omni/Agent/Engine.hs @@ -254,7 +254,11 @@ data EngineConfig = EngineConfig { engineLLM :: LLM, engineOnCost :: Int -> Int -> IO (), engineOnActivity :: Text -> IO (), - engineOnToolCall :: Text -> Text -> IO () + engineOnToolCall :: Text -> Text -> IO (), + engineOnAssistant :: Text -> IO (), + engineOnToolResult :: Text -> Bool -> Text -> IO (), + engineOnComplete :: IO (), + engineOnError :: Text -> IO () } defaultEngineConfig :: EngineConfig @@ -263,7 +267,11 @@ defaultEngineConfig = { engineLLM = defaultLLM, engineOnCost = \_ _ -> pure (), engineOnActivity = \_ -> pure (), - engineOnToolCall = \_ _ -> pure () + engineOnToolCall = \_ _ -> pure (), + engineOnAssistant = \_ -> pure (), + engineOnToolResult = \_ _ _ -> pure (), + engineOnComplete = pure (), + engineOnError = \_ -> pure () } data AgentResult = AgentResult @@ -495,26 +503,30 @@ runAgent engineCfg agentCfg userPrompt = do loop :: LLM -> [Tool] -> Map.Map Text Tool -> [Message] -> Int -> Int -> Int -> IO (Either Text AgentResult) loop llm tools' toolMap msgs iteration totalCalls totalTokens - | iteration >= maxIter = - pure - <| Left - <| "Max iterations (" - <> tshow maxIter - <> ") reached" + | iteration >= maxIter = do + let errMsg = "Max iterations (" <> tshow maxIter <> ") reached" + engineOnError engineCfg errMsg + pure <| Left errMsg | otherwise = do engineOnActivity engineCfg <| "Iteration " <> tshow (iteration + 1) result <- chatWithUsage llm tools' msgs case result of - Left err -> pure (Left err) + Left err -> do + engineOnError engineCfg err + pure (Left err) Right chatRes -> do let msg = chatMessage chatRes tokens = maybe 0 usageTotalTokens (chatUsage chatRes) cost = estimateCost (llmModel llm) tokens engineOnCost engineCfg tokens cost let newTokens = totalTokens + tokens + let assistantText = msgContent msg + unless (Text.null assistantText) <| + engineOnAssistant engineCfg assistantText case msgToolCalls msg of Nothing -> do engineOnActivity engineCfg "Agent completed" + engineOnComplete engineCfg pure <| Right <| AgentResult @@ -526,6 +538,7 @@ runAgent engineCfg agentCfg userPrompt = do } Just [] -> do engineOnActivity engineCfg "Agent completed (empty tool calls)" + engineOnComplete engineCfg pure <| Right <| AgentResult @@ -552,22 +565,22 @@ executeToolCalls engineCfg toolMap = traverse executeSingle argsText = fcArguments (tcFunction tc) callId = tcId tc engineOnActivity engineCfg <| "Executing tool: " <> name + engineOnToolCall engineCfg name argsText case Map.lookup name toolMap of Nothing -> do let errMsg = "Tool not found: " <> name - engineOnToolCall engineCfg name errMsg + engineOnToolResult engineCfg name False errMsg pure <| Message ToolRole errMsg Nothing (Just callId) Just tool -> do case Aeson.decode (BL.fromStrict (TE.encodeUtf8 argsText)) of Nothing -> do let errMsg = "Invalid JSON arguments: " <> argsText - engineOnToolCall engineCfg name errMsg + engineOnToolResult engineCfg name False errMsg pure <| Message ToolRole errMsg Nothing (Just callId) Just args -> do resultValue <- toolExecute tool args let resultText = TE.decodeUtf8 (BL.toStrict (Aeson.encode resultValue)) - summary = Text.take 100 resultText - engineOnToolCall engineCfg name summary + engineOnToolResult engineCfg name True resultText pure <| Message ToolRole resultText Nothing (Just callId) estimateCost :: Text -> Int -> Int -- cgit v1.2.3