# Haskell Frontend Architecture

The Haskell frontend provides a streaming WebSocket client for the Cognition hub's JSON-RPC interface, with typed plugin APIs built on top.

## Architecture Layers

```
┌─────────────────────────────────────────────────────────────┐
│                        CLI (app/Main.hs)                    │
│         cognition health | cognition bash execute <cmd>     │
├─────────────────────────────────────────────────────────────┤
│                  Typed Plugin APIs                          │
│         Cognition.Health      Cognition.Bash                │
│         check :: Stream       execute :: Stream             │
├─────────────────────────────────────────────────────────────┤
│                    Low-Level RPC                            │
│                   Cognition.Rpc.Client                      │
│     hubRpc :: HubConnection -> Text -> Value -> Stream      │
├─────────────────────────────────────────────────────────────┤
│                    WebSocket Transport                      │
│              JSON-RPC 2.0 over WebSocket                    │
└─────────────────────────────────────────────────────────────┘
```

## Module Structure

```
frontend-hs/
├── src/
│   └── Cognition/
│       ├── Rpc.hs           -- Re-exports low-level API
│       ├── Rpc/
│       │   ├── Types.hs     -- JSON-RPC protocol types
│       │   └── Client.hs    -- WebSocket client implementation
│       ├── Bash.hs          -- Typed Bash plugin API
│       └── Health.hs        -- Typed Health plugin API
└── app/
    └── Main.hs              -- CLI tool
```

## Core Primitive

```haskell
hubRpc :: HubConnection -> Text -> Value -> Stream (Of HubStreamItem) IO ()
```

This is the fundamental building block. It:
1. Sends a subscription request to the hub
2. Returns a lazy stream of `HubStreamItem` values
3. Completes when `StreamDone` or `StreamError` is received

### Cognition.Rpc.Types

Defines the JSON-RPC 2.0 protocol types:

```haskell
-- Request/Response
data RpcRequest          -- Outgoing JSON-RPC request
data RpcResponse         -- Success or error response
data RpcError            -- Error details

-- Subscriptions
data SubscriptionNotification  -- Server push notification
data SubNotifParams            -- Notification payload

-- Stream items (from hub)
data HubStreamItem
  = StreamProgress { path, message, percentage }
  | StreamData     { path, contentType, data }
  | StreamError    { path, error, recoverable }
  | StreamDone     { path }
```

### Cognition.Rpc.Client

Manages WebSocket connection and message dispatch:

```haskell
data HubConnection = HubConnection
  { hcConnection    :: Connection           -- WebSocket handle
  , hcNextId        :: IORef Int            -- Request ID counter
  , hcSubscriptions :: TVar (Map SubscriptionId (TQueue Value))
  , hcPendingReqs   :: TVar (Map RequestId PendingRequest)
  , hcReaderThread  :: Async ()             -- Background reader
  }

connect    :: HubConfig -> IO HubConnection
disconnect :: HubConnection -> IO ()
hubRpc     :: HubConnection -> Text -> Value -> Stream (Of HubStreamItem) IO ()
```

## Concurrency Model

```
┌─────────────────────────────────────────────────────────────┐
│                     Main Thread                              │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ hubRpc "bash_execute" params                        │    │
│  │   1. Create queue + respVar                         │    │
│  │   2. Register in hcPendingReqs                      │    │
│  │   3. Send request over WebSocket                    │    │
│  │   4. Block on respVar (wait for response)           │    │
│  │   5. Stream from queue until Done/Error             │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘
                           │
                           │ WebSocket
                           ▼
┌─────────────────────────────────────────────────────────────┐
│                   Reader Thread (async)                      │
│  ┌─────────────────────────────────────────────────────┐    │
│  │ forever $ do                                        │    │
│  │   msg <- receiveData conn                           │    │
│  │   case msg of                                       │    │
│  │     Response → register sub queue, signal respVar   │    │
│  │     Notification → write to subscription queue      │    │
│  └─────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────┘
```

## Race Condition Fix

The critical insight: notifications can arrive immediately after the response, before the main thread registers the subscription queue.

**Solution**: The reader thread registers the queue atomically when processing the response:

```haskell
handleResponse val = do
  case resp of
    RpcSuccess _ result -> do
      case fromJSON result of
        Success subId -> atomically $ do
          modifyTVar' subs $ Map.insert subId queue  -- Register FIRST
          putTMVar respVar resp                       -- Then signal
```

This ensures the queue is in the map before any notifications can arrive (WebSocket messages are serialized).

## Data Flow

```
1. Client calls hubRpc "health_check" Null

2. → {"jsonrpc":"2.0","method":"health_check","params":null,"id":1}

3. ← {"jsonrpc":"2.0","id":1,"result":"sub_12345"}
   (Reader registers queue for "sub_12345", signals respVar)

4. ← {"jsonrpc":"2.0","method":"subscription","params":{
       "subscription":"sub_12345",
       "result":{"event":"data","data":{...}}
     }}
   (Reader writes to queue)

5. ← {"jsonrpc":"2.0","method":"subscription","params":{
       "subscription":"sub_12345",
       "result":{"event":"done","data":{"path":{...}}}
     }}
   (Reader writes to queue, hubRpc sees Done and returns)
```

## Typed Plugin APIs

Built on top of `hubRpc`, these modules provide type-safe access to plugins:

### Cognition.Bash

```haskell
-- | Events emitted by bash execution
data BashEvent
  = Stdout { line :: Text }
  | Stderr { line :: Text }
  | Exit   { code :: Int }

-- | Execute a bash command
execute :: HubConnection -> Text -> Stream (Of BashEvent) IO ()
```

### Cognition.Health

```haskell
-- | Events emitted by health check
data HealthEvent
  = Status { status :: Text, uptimeSeconds :: Int, timestamp :: Int }

-- | Check hub health
check :: HubConnection -> Stream (Of HealthEvent) IO ()
```

## Usage Examples

### Low-level API

```haskell
import Cognition.Rpc
import qualified Streaming.Prelude as S

main = do
  conn <- connect defaultConfig
  S.mapM_ print $ hubRpc conn "bash_execute" (toJSON ["echo hello"])
  disconnect conn
```

### Typed Plugin API (recommended)

```haskell
import Cognition.Rpc (connect, disconnect, defaultConfig)
import qualified Cognition.Bash as Bash
import qualified Cognition.Health as Health
import qualified Streaming.Prelude as S

main = do
  conn <- connect defaultConfig

  -- Health check - fully typed
  S.mapM_ handleHealth $ Health.check conn

  -- Bash execution - fully typed
  S.mapM_ handleBash $ Bash.execute conn "echo hello"

  disconnect conn

handleHealth :: Health.HealthEvent -> IO ()
handleHealth (Health.Status s uptime _) =
  putStrLn $ "Status: " ++ show s ++ " (uptime: " ++ show uptime ++ "s)"

handleBash :: Bash.BashEvent -> IO ()
handleBash (Bash.Stdout line) = T.putStrLn line
handleBash (Bash.Stderr line) = T.hPutStrLn stderr line
handleBash (Bash.Exit code)   = putStrLn $ "Exit: " ++ show code
```

## CLI

```bash
# Health check
cognition health
# → Status: healthy (uptime: 123s)

# Execute bash command
cognition bash execute echo hello world
# → hello world

cognition bash execute ls -la
# → drwxr-xr-x  5 user  staff  160 Dec  9 00:00 .
# → ...
```

## Dependencies

- `websockets` - WebSocket client
- `streaming` - Lazy streaming with effects
- `aeson` - JSON encoding/decoding
- `stm` - Software transactional memory (TVar, TQueue, TMVar)
- `async` - Async thread management

## Configuration

```haskell
data HubConfig = HubConfig
  { hubHost :: String   -- Default: "127.0.0.1"
  , hubPort :: Int      -- Default: 9944
  , hubPath :: String   -- Default: "/"
  }
```
