---
title: "Code Extraction"
---

# 08 — Code Extraction

## The Problem

When Ollama generates code, it wraps it in markdown code blocks:

````
Here's the script:

```python
print("Hello, world!")
```

Save this as hello.py and run it.
````

If we want to **run** that code automatically, we need to pull it out of the markdown — strip the backticks, get the raw code, and save it as a runnable file.

## The Extractor

The `2-extract` tool does exactly this. It reads `ollama-output.md` and splits it into:

- `reasoning.md` — the model's internal thinking (between `Thinking...` and `...done thinking.`)
- `response.md` — the model's actual response (everything after `...done thinking.`)
- `code` — the first extracted code block, made executable

### The Script

Here's the extractor generated by our `gemma4:26b` model:

````typescript
#!/usr/bin/env bun
import { writeFileSync, chmodSync } from "node:fs";
import path from "node:path";

async function main() {
  const args = process.argv.slice(2);
  if (args.length < 1) {
    console.error("Usage: ./extract.ts <directory> [filename]");
    process.exit(1);
  }

  const dir = args[0];
  const filename = args[1] || "ollama-output.md";
  const inputPath = path.join(dir, filename);

  try {
    const content = await Bun.file(inputPath).text();
    const lines = content.split(/\r?\n/);

    const reasoning: string[] = [];
    const response: string[] = [];
    const code: string[] = [];

    let state: "searching" | "reasoning" | "response" = "searching";
    let isInsideCodeBlock = false;
    let codeExtracted = false;

    for (const line of lines) {
      // Separator detection
      if (state === "searching" && line === "Thinking...") {
        state = "reasoning";
        continue;
      }
      if (state === "reasoning" && line === "...done thinking.") {
        state = "response";
        continue;
      }

      if (state === "reasoning") {
        reasoning.push(line);
      } else if (state === "response") {
        response.push(line);

        // Code block extraction logic
        if (!codeExtracted && line.startsWith("```")) {
          if (!isInsideCodeBlock) {
            isInsideCodeBlock = true;
          } else {
            isInsideCodeBlock = false;
            codeExtracted = true;
          }
          continue; // Skip the fence line
        }

        if (isInsideCodeBlock) {
          code.push(line);
        }
      }
    }

    // Write outputs
    writeFileSync(path.join(dir, "reasoning.md"), reasoning.join("\n"));
    writeFileSync(path.join(dir, "response.md"), response.join("\n"));

    const codePath = path.join(dir, "code");
    writeFileSync(codePath, code.join("\n"));
    chmodSync(codePath, 0o755);

    console.log("Extraction complete.");
  } catch (error) {
    console.error(`Error: ${error}`);
    process.exit(1);
  }
}

main();
````

## How It Works

The extractor uses a simple state machine:

````
                    "Thinking..."
SEARCHING ─────────────────────────► REASONING
                                          │
                              "...done thinking."
                                          │
                                          ▼
                                      RESPONSE
                                          │
                              sees ``` (open)
                                          │
                                          ▼
                                   INSIDE CODE BLOCK
                                          │
                              sees ``` (close)
                                          │
                                          ▼
                                  CODE EXTRACTED
````

1. **SEARCHING** — scanning for the start of reasoning
2. **REASONING** — collecting lines between `Thinking...` and `...done thinking.`
3. **RESPONSE** — collecting everything after, while watching for code blocks
4. **INSIDE CODE BLOCK** — collecting code lines (strips the ``` markers)

## Why Separate Reasoning and Response?

Ollama models (like Gemini-family models) produce two sections:

- **Thinking/reasoning** — the model's internal monologue. This is _not_ meant for the user. It's where the model plans, considers options, and works through the problem.
- **Response** — the actual answer. This is what you'd show a user.

Separating them lets you:

- Store reasoning separately for debugging (why did the model make that choice?)
- Feed only the response to downstream tools
- Compare reasoning across runs to see how the model's thinking changes

## Usage

```bash
# Run a prompt
cat my-prompt/prompt.md | ollama run gemma4:26b --nowordwrap | tee my-prompt/ollama-output.md

# Extract the code
./2-extract/code my-prompt

# See what we got
ls my-prompt/
# reasoning.md  response.md  code  prompt.md  ollama-output.md

# The code is already executable!
./my-prompt/code
```

## The Test Pattern

The extractor also supports automatic testing. If there's a `test` file in the directory, the prompt runner will:

1. Make it executable (`chmod +x`)
2. Run it
3. Capture output to `test-output.md`

This is how we close the automation loop: prompt → generate → extract → test → capture results → feed back.

## What You've Learned

- The extractor parses Ollama's output format (thinking → response → code blocks)
- A state machine is a simple, reliable way to parse structured text
- Separating reasoning from response is useful for debugging
- Making extracted code executable lets us run it immediately
- The test pattern captures results for the self-improving loop

**Next: [Projects →](/run-ai-locally/projects/)** — five real tools the AI built for you.
