Create Op.hs with OpF GADT and Free Monad

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

Description

Edit

Create Omni/Agent/Op.hs with the core free monad over inference operations.

Context

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.

Deliverables

Create Omni/Agent/Op.hs containing:

1. The OpF GADT

{-# 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

2. Supporting Types

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

3. The Op Type

type Op s = Free (OpF s)

4. Smart Constructors

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)

5. Dependencies

Add to the module header:

-- : dep free
-- : dep aeson
-- : dep text
-- : dep time

The free package provides Control.Monad.Free.

Notes

  • Look at existing types in Engine.hs (Message, ToolCall, etc.) - reuse or adapt as needed
  • Event type will be fleshed out in the Trace.hs task, use a placeholder for now
  • Trace type will be fleshed out in the Trace.hs task, use a placeholder for now
  • This module should compile and have basic unit tests for the Functor/Monad laws

Testing

Add basic tests:

  • Op is a Functor (fmap id = id)
  • Op is a Monad (left identity, right identity, associativity)
  • Smart constructors produce expected structure

Files to Read First

  • Omni/Agent/ARCHITECTURE.md (full design)
  • Omni/Agent/Engine.hs (current implementation, types to reuse)
  • Omni/Agent/Events.hs (current event types)

Timeline (2)

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