diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,5 @@
+# Revision history for OllamaHoles
+
+## 0.1.0.0 -- 2024-04-25
+
+* First version. Released on an unsuspecting world.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2025 Matthias Pall Gissurarson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,75 @@
+# Ollama Holes
+
+![image](https://github.com/user-attachments/assets/649ffcd2-0560-47d6-bbbe-74bae08cbb70)
+
+## Introduction
+This is an example of a typed-hole plugin for GHC that uses the [Ollama](https://ollama.com/) to host a local LLM to fill in holes in Haskell code.
+
+
+## Example
+Given 
+
+```haskell
+{-# OPTIONS_GHC -fplugin=GHC.Plugin.OllamaHoles #-}
+{-# OPTIONS_GHC -fplugin-opt=GHC.Plugin.OllamaHoles:model=gemma3:27b #-}
+{-# OPTIONS_GHC -fplugin-opt=GHC.Plugin.OllamaHoles:n=5 #-}
+
+module Main where
+
+import Data.List
+
+
+main :: IO ()
+main = do let k = (_b :: [Int] -> [String])
+          print (k [1,2,3])
+
+```
+
+We get the following output:
+
+
+```text
+    Main.hs:12:20: error: [GHC-88464]
+        • Found hole: _b :: [Int] -> [String]
+        Or perhaps ‘_b’ is mis-spelled, or not in scope
+        • In the expression: _b :: [Int] -> [String]
+        In an equation for ‘k’: k = (_b :: [Int] -> [String])
+        In the expression:
+            do let k = (_b :: [Int] -> [String])
+            print (k [1, 2, ....])
+        • Relevant bindings include
+            k :: [Int] -> [String] (bound at Main.hs:12:15)
+            main :: IO () (bound at Main.hs:12:1)
+        Valid hole fits include
+            \x -> map show x
+            map (show)
+            \x -> map (\y -> "Number: " ++ show y) x
+            \x -> replicate (length x) "Hello"
+            []
+    |
+    12 | main = do let k = (_b :: [Int] -> [String])
+    |                    ^^
+    Error: cabal: Failed to build exe:main from OllamaHolesTest-1.0.0.
+```
+
+## Installation
+
+1. Install [Ollama](https://ollama.com/download)
+2. Install the `gemma3:27b` model (or any other model you prefer) using the following command:
+
+```bash
+
+ollama pull gemma3:27b
+```
+3. Clone this repository and navigate to the directory, and build the project using:
+
+```bash
+cabal build
+```
+4. Run the example using:
+
+```bash
+cabal build Test
+```
+
+5. Enjoy! If you want to change the underlying model, make sure to pass the model name via the plugin arguments (see example)
diff --git a/ollama-holes-plugin.cabal b/ollama-holes-plugin.cabal
new file mode 100644
--- /dev/null
+++ b/ollama-holes-plugin.cabal
@@ -0,0 +1,115 @@
+cabal-version:      3.0
+-- The cabal-version field refers to the version of the .cabal specification,
+-- and can be different from the cabal-install (the tool) version and the
+-- Cabal (the library) version you are using. As such, the Cabal (the library)
+-- version used must be equal or greater than the version stated in this field.
+-- Starting from the specification version 2.2, the cabal-version field must be
+-- the first thing in the cabal file.
+
+-- Initial package description 'OllamaHoles' generated by
+-- 'cabal init'. For further documentation, see:
+--   http://haskell.org/cabal/users-guide/
+--
+-- The name of the package.
+name:               ollama-holes-plugin
+
+-- The package version.
+-- See the Haskell package versioning policy (PVP) for standards
+-- guiding when and how versions should be incremented.
+-- https://pvp.haskell.org
+-- PVP summary:     +-+------- breaking API changes
+--                  | | +----- non-breaking API additions
+--                  | | | +--- code changes with no API change
+version:            0.1.0.0
+
+-- A short (one-line) description of the package.
+synopsis: A typed-hole plugin that uses LLMs via Ollama to generate valid hole-fits
+
+-- A longer description of the package.
+description: This package provides a GHC plugin that uses LLMs via Ollama to generate valid hole-fits.
+             .
+             The following flags are available:
+             .
+             To specify the model to use:
+             .
+             > -fplugin-opt=GHC.Plugin.OllamaHoles:model=<model_name>
+             .
+             To specify how many fits to generate (passed to the model)
+             .
+             > -fplugin-opt=GHC.Plugin.OllamaHoles:n=5
+             .
+             To enable debug output:
+             .
+             > -fplugin-opt=GHC.Plugin.OllamaHoles:debug=True
+             .
+             Before using this plugin, make sure you have the Ollama CLI installed and the model
+             you want to use is available. You can install the Ollama CLI by following the
+             instructions at [https://ollama.com/download](https://ollama.com/download),
+             and you can install the default model (gemma3:27b) by running `ollama pull gemma3:27b`.
+             .
+             Note that the speed and quality of the hole-fits generated by the plugin depends on
+             the model you use, and the default model requires a GPU to run efficiently.
+             For a smaller model, we suggest `gemma3:4b-it-qat`, or `deepcoder:1.5b`.
+
+
+
+-- The license under which the package is released.
+license:            MIT
+
+-- The file containing the license text.
+license-file:       LICENSE
+
+-- The package author(s).
+author:             Matthias Pall Gissurarson <mpg@mpg.is>
+
+-- An email address to which users can send suggestions, bug reports, and patches.
+maintainer:         Matthias Pall Gissurarson <mpg@mpg.is>
+
+
+-- A copyright notice.
+copyright:          2025- Matthias Pall Gissurarson  
+category:           Development, Compiler Plugin
+tested-with:        GHC == 9.6.*
+build-type:         Simple
+
+-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.
+extra-doc-files:    CHANGELOG.md
+                    README.md
+
+homepage:       https://github.com/Tritlo/OllamaHoles
+
+-- Extra source files to be distributed with the package, such as examples, or a tutorial module.
+-- extra-source-files:
+
+source-repository head
+  type:     git
+  location: git://github.com/Tritlo/OllamaHoles.git
+
+common warnings
+    ghc-options: -Wall
+
+library
+    -- Import common warning flags.
+    import:           warnings
+
+    -- Modules exported by the library.
+    exposed-modules:  GHC.Plugin.OllamaHoles
+
+    -- Modules included in this library but not exported.
+    -- other-modules:
+
+    -- LANGUAGE extensions used by modules in this package.
+    -- other-extensions:
+
+    -- Other library packages from which modules are imported.
+    build-depends:    base ^>=4.18.2.1,
+                      ghc ^>=9.6,
+                      ollama-haskell ^>= 0.1,
+                      text ^>= 2.1,
+
+
+    -- Directories containing source files.
+    hs-source-dirs:   src
+
+    -- Base language which the package is written in.
+    default-language: GHC2021
diff --git a/src/GHC/Plugin/OllamaHoles.hs b/src/GHC/Plugin/OllamaHoles.hs
new file mode 100644
--- /dev/null
+++ b/src/GHC/Plugin/OllamaHoles.hs
@@ -0,0 +1,178 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+
+-- | The Ollama plugin for GHC
+module GHC.Plugin.OllamaHoles (plugin) where
+
+import Control.Monad (unless, when)
+import Data.Char (isSpace)
+import Data.Text (Text)
+import Data.Text qualified as T
+import Data.Text.IO qualified as T
+import GHC.Plugins hiding ((<>))
+import GHC.Tc.Types
+import GHC.Tc.Types.Constraint (Hole (..), ctLocEnv, ctLocSpan)
+import GHC.Tc.Utils.Monad (getGblEnv, newTcRef)
+import Ollama (GenerateOps (..))
+import Ollama qualified
+
+-- | Options for the LLM model
+genOps :: Ollama.GenerateOps
+genOps =
+    Ollama.defaultGenerateOps
+        { modelName = ""
+        , prompt = ""
+        }
+
+promptTemplate :: Text
+promptTemplate =
+    "You are a typed-hole plugin within GHC, the Glasgow Haskell Compiler.\n"
+        <> "You are given a hole in a Haskell program, and you need to fill it in.\n"
+        <> "The hole is represented by the following information:\n"
+        <> "{module}\n{location}\n{imports}\n{hole_var}\n{hole_type}\n{relevant_constraints}\n{local_env}\n{global_env}\n{candidate_fits}\n\n"
+        <> "Provide one or more Haskell expressions that could fill this hole.\n"
+        <> "This means coming up with an expression of the correct type that satisfies the constraints.\n"
+        <> "Pay special attention to the type of the hole, specifically whether it is a function.\n"
+        <> "Make sure you synthesize an expression that matches the type of the hole.\n"
+        <> "Output ONLY the raw Haskell expression(s), one per line.\n"
+        <> "Do not include explanations, introductions, or any surrounding text.\n"
+        <> "Feel free to include any other functions from the list of imports to generate more complicated expressions.\n"
+        <> "Output a maximum of {numexpr} expresssions.\n"
+
+-- | Ollama plugin for GHC
+plugin :: Plugin
+plugin =
+    defaultPlugin
+        { holeFitPlugin = \opts ->
+            Just $
+                HoleFitPluginR
+                    { hfPluginInit = newTcRef ()
+                    , hfPluginStop = \_ -> return ()
+                    , hfPluginRun =
+                        const
+                            HoleFitPlugin
+                                { candPlugin = \_ c -> return c -- Don't filter candidates
+                                , fitPlugin = \hole fits -> do
+                                    let Flags{..} = parseFlags opts
+                                    dflags <- getDynFlags
+                                    gbl_env <- getGblEnv
+                                    let mod_name = moduleNameString $ moduleName $ tcg_mod gbl_env
+                                        imports = tcg_imports gbl_env
+                                    liftIO $ do
+                                        available_models <- Ollama.list
+                                        case available_models of
+                                            Nothing -> T.putStrLn "--- Ollama plugin: No models available.--"
+                                            Just (Ollama.Models models) -> do
+                                                unless (any ((== model_name) . Ollama.name) models) $
+                                                    error $
+                                                        "--- Ollama plugin: Model "
+                                                            <> T.unpack model_name
+                                                            <> " not found. "
+                                                            <> "Use `ollama pull` to download the model, or specify another model using "
+                                                            <> "`-fplugin-opt=GHC.Plugin.OllamaHoles:model=<model_name>` ---"
+                                        when debug $ T.putStrLn "--- Ollama Plugin: Hole Found ---"
+                                        let mn = "Module: " <> mod_name
+                                        let lc = "Location: " <> showSDoc dflags (ppr $ ctLocSpan . hole_loc <$> th_hole hole)
+                                        let im = "Imports: " <> showSDoc dflags (ppr $ moduleEnvKeys $ imp_mods imports)
+
+                                        case th_hole hole of
+                                            Just h -> do
+                                                let lcl_env = ctLocEnv (hole_loc h)
+                                                let hv = "Hole variable: _" <> occNameString (occName $ hole_occ h)
+                                                let ht = "Hole type: " <> showSDoc dflags (ppr $ hole_ty h)
+                                                let rc = "Relevant constraints: " <> showSDoc dflags (ppr $ th_relevant_cts hole)
+                                                let le = "Local environment (bindings): " <> showSDoc dflags (ppr $ tcl_rdr lcl_env)
+                                                let ge = "Global environment (bindings): " <> showSDoc dflags (ppr $ tcg_binds gbl_env)
+                                                let cf = "Candidate fits: " <> showSDoc dflags (ppr fits)
+                                                let prompt' =
+                                                        replacePlaceholders
+                                                            promptTemplate
+                                                            [ ("{module}", mn)
+                                                            , ("{location}", lc)
+                                                            , ("{imports}", im)
+                                                            , ("{hole_var}", hv)
+                                                            , ("{hole_type}", ht)
+                                                            , ("{relevant_constraints}", rc)
+                                                            , ("{local_env}", le)
+                                                            , ("{global_env}", ge)
+                                                            , ("{candidate_fits}", cf)
+                                                            , ("{numexpr}", show num_expr)
+                                                            ]
+                                                res <- Ollama.generate genOps{prompt = prompt', modelName = model_name}
+                                                case res of
+                                                    Right rsp -> do
+                                                        let lns = (preProcess . T.lines) $ Ollama.response_ rsp
+                                                        when debug $ do
+                                                            T.putStrLn $ "--- Ollama Plugin: Prompt ---\n" <> prompt'
+                                                            T.putStrLn $ "--- Ollama Plugin: Response ---\n" <> Ollama.response_ rsp
+                                                        let fits' = map (RawHoleFit . text . T.unpack) lns
+                                                        -- Return the generated fits
+                                                        return fits'
+                                                    Left err -> do
+                                                        when debug $
+                                                            putStrLn $
+                                                                "Ollama plugin failed to generate a response.\n" <> err
+                                                        -- Return the original fits without modification
+                                                        return fits
+                                            Nothing -> return fits
+                                }
+                    }
+        }
+
+-- | Preprocess the response to remove empty lines, lines with only spaces, and code blocks
+preProcess :: [Text] -> [Text]
+preProcess [] = []
+preProcess (ln : lns) | should_drop = preProcess lns
+  where
+    should_drop :: Bool
+    should_drop =
+        T.null ln
+            || T.all isSpace ln
+            || T.isPrefixOf "```" ln
+preProcess (ln : lns) = transform ln : preProcess lns
+  where
+    transform :: Text -> Text
+    transform = id
+
+-- | Command line options for the plugin
+data Flags = Flags
+    { model_name :: Text
+    , num_expr :: Int
+    , debug :: Bool
+    }
+
+-- | Default flags for the plugin
+defaultFlags :: Flags
+defaultFlags =
+    Flags
+        { model_name = "gemma3:27b-it-qat"
+        , num_expr = 5
+        , debug = False
+        }
+
+-- | Parse command line options
+parseFlags :: [CommandLineOption] -> Flags
+parseFlags = parseFlags' defaultFlags
+  where
+    parseFlags' :: Flags -> [CommandLineOption] -> Flags
+    parseFlags' flags [] = flags
+    parseFlags' flags (opt : opts)
+        | T.isPrefixOf "model=" (T.pack opt) =
+            let model_name = T.drop (T.length "model=") (T.pack opt)
+             in parseFlags' flags{model_name = model_name} opts
+    parseFlags' flags (opt : opts)
+        | T.isPrefixOf "debug=" (T.pack opt) =
+            let debug = T.unpack $ T.drop (T.length "debug=") (T.pack opt)
+             in parseFlags' flags{debug = read debug} opts
+    parseFlags' flags (opt : opts)
+        | T.isPrefixOf "n=" (T.pack opt) =
+            let num_expr = T.unpack $ T.drop (T.length "n=") (T.pack opt)
+             in parseFlags' flags{num_expr = read num_expr} opts
+    parseFlags' flags _ = flags
+
+-- | Helper function to replace placeholders in a template string
+replacePlaceholders :: Text -> [(Text, String)] -> Text
+replacePlaceholders = foldl replacePlaceholder
+  where
+    replacePlaceholder :: Text -> (Text, String) -> Text
+    replacePlaceholder str (placeholder, value) = T.replace placeholder (T.pack value) str
