Create Omni/Agent/Op.hs with the core free monad over inference operations.
The current agent system (Engine.hs) uses a monolithic recursive loop. This refactor reifies operations as data using a free monad, enabling composition, parallelism, and introspection.
Read Omni/Agent/ARCHITECTURE.md for full design rationale.
Create Omni/Agent/Op.hs containing:
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DeriveFunctor #-}
data OpF s next where
-- Inference primitive (the irreducible core)
Infer :: Model -> Prompt -> (Response -> next) -> OpF s next
-- Parallel composition
Par :: [Op s a] -> ([a] -> next) -> OpF s next
Race :: [Op s a] -> (a -> next) -> OpF s next
-- State access (polymorphic in state type s)
Get :: (s -> next) -> OpF s next
Put :: s -> next -> OpF s next
Modify :: (s -> s) -> next -> OpF s next
-- Resource limits
Limit :: Budget -> Op s a -> (Either Exhausted a -> next) -> OpF s next
Timeout :: Seconds -> Op s a -> (Either TimedOut a -> next) -> OpF s next
-- Tool execution (tools are IO)
Tool :: ToolName -> Value -> (Value -> next) -> OpF s next
-- Observability (first-class)
Emit :: Event -> next -> OpF s next
GetTrace :: (Trace -> next) -> OpF s next
-- Control
Checkpoint :: Text -> next -> OpF s next
Check :: (Maybe Steering -> next) -> OpF s next
newtype Model = Model Text
newtype Prompt = Prompt [Message] -- or Text, decide based on Engine.hs
data Response = Response { responseContent :: Text, responseToolCalls :: [ToolCall] }
data Budget = Budget { budgetCents :: Double, budgetTokens :: Int }
data Exhausted = CostExhausted | TokensExhausted | TimeoutExhausted
newtype Seconds = Seconds Int
data TimedOut = TimedOut
data Steering = SteeringMessage Text | SteeringCancel
type ToolName = Text
type Op s = Free (OpF s)
infer :: Model -> Prompt -> Op s Response
par :: [Op s a] -> Op s [a]
race :: [Op s a] -> Op s a
get :: Op s s
put :: s -> Op s ()
modify :: (s -> s) -> Op s ()
limit :: Budget -> Op s a -> Op s (Either Exhausted a)
timeout :: Seconds -> Op s a -> Op s (Either TimedOut a)
tool :: ToolName -> Value -> Op s Value
emit :: Event -> Op s ()
getTrace :: Op s Trace
checkpoint :: Text -> Op s ()
check :: Op s (Maybe Steering)
Add to the module header:
-- : dep free
-- : dep aeson
-- : dep text
-- : dep time
The free package provides Control.Monad.Free.
Add basic tests: