---
order: 5
title: Lab 5 - API & Harnesses
description: Generate an Open WebUI API key, connect one of three coding harnesses, and build a small Zork-style game.
---

<!-- breakout-style: instruction-rails -->
<!-- step-style: underline -->
<!-- objective-style: divider -->

# Lab 5 - API & Harnesses

In this lab, we will:

- Generate a personal API key inside Open WebUI
- Install one of three coding harnesses
- Configure that harness to talk to Open WebUI as the backend
- Use the harness to build a small Zork-style game

<div class="lab-callout lab-callout--info">
  <strong>Lab Flow Guide</strong><br />
  This lab stays on a single high-level track, but Objectives 2 and 3 branch into three harness paths.<br />
  Pick one harness, complete its branch, then rejoin the common Zork objective at the end.
</div>

To start this lab, one web service has been preconfigured:

- Open WebUI - {{service-url:open-webui}}

## Objective 1 Execute: Generate an Open WebUI API Key

Before we install any harness, we need a key that lets the harness call the same model backend exposed through Open WebUI.

### Execute: Sign in to Open WebUI

1. Navigate to `{{service-url:open-webui}}`.
2. Sign in with the same account you used in Lab 4, or the credentials supplied by your instructor.
3. Confirm that you can reach the normal chat screen before continuing.

### Execute: Create a personal access token

According to the Open WebUI reference docs, API keys are created from **Settings -> Account** and authenticate with the same permissions as the user who created them.

1. Click your avatar in the lower-left corner.

<figure style="text-align: center;">
  <a href="https://i.imgur.com/oZuZwWQ.png" target="_blank">
    <img 
      src="https://i.imgur.com/oZuZwWQ.png" 
      style="width: 50%; display: block; margin-left: auto; margin-right: auto; border: 5px solid black;">
  </a>
  <figcaption style="margin-top: 8px; font-size: 1.1em;">
    User Settings
  </figcaption>
</figure>
<br>

2. Open **Settings**.
3. Open **Account**.
4. Locate the **API Keys** section.

<figure style="text-align: center;">
  <a href="https://i.imgur.com/oDe6cpE.png" target="_blank">
    <img 
      src="https://i.imgur.com/oDe6cpE.png" 
      style="width: 50%; display: block; margin-left: auto; margin-right: auto; border: 5px solid black;">
  </a>
  <figcaption style="margin-top: 8px; font-size: 1.1em;">
    API Key
  </figcaption>
</figure>
<br>

6. Copy the key immediately and store it somewhere safe for the duration of the lab.

<div class="lab-callout lab-callout--warning">
  <strong>If you do not see API Keys:</strong> Open WebUI requires the feature to be enabled globally, and your user account needs permission to generate keys. Ask your instructor for help before continuing.
</div>

### Execute: Sanity-check the key from the terminal

Run a quick authenticated request against the Open WebUI model list endpoint. You should receive JSON back instead of an authentication error.

```bash
curl {{service-url:open-webui:/api}}/models \
  -H "Authorization: Bearer YOUR_OPENWEBUI_API_KEY"
```

If this request works, your harness will use the same key for later steps.

---

## Objective 2 Execute: Choose and Install a Harness

All three branches ultimately talk to the same Open WebUI backend. The difference is the user interface and configuration style for each harness.

<div class="lab-harness-chooser" role="group" aria-label="Harness installation paths">
  <button type="button" class="lab-harness-card" data-harness-choice="opencode" aria-pressed="false">
    <span class="lab-harness-card__tag">Path A</span>
    <strong>OpenCode</strong>
    <span>Terminal-first coding agent</span>
  </button>
  <button type="button" class="lab-harness-card" data-harness-choice="kilocode" aria-pressed="false">
    <span class="lab-harness-card__tag">Path B</span>
    <strong>Kilo Code VS Code</strong>
    <span>Editor-driven coding assistant</span>
  </button>
  <button type="button" class="lab-harness-card" data-harness-choice="droid" aria-pressed="false">
    <span class="lab-harness-card__tag">Path C</span>
    <strong>Factory Droid</strong>
    <span>Advanced CLI harness with powerful Spec Driven Development (Missions)</span>
  </button>
</div>

<p>Select a path to reveal that harness's instructions throughout the rest of the lab. Select the same card again if you want to hide the harness-specific instructions and return to the shared overview.</p>

### Execute: Install the harness you want to use

<section class="lab-harness-branch" id="opencode-install" data-harness-branch="opencode">
  <p class="lab-harness-branch__eyebrow">Path A</p>
  <h3>Install OpenCode</h3>
  <p>OpenCode is a terminal-native coding agent. Its official docs recommend either the install script or the npm package.</p>
  <pre><code class="language-bash">curl -fsSL https://opencode.ai/install | bash
opencode --version</code></pre>
  <p>If you prefer npm and already have Node.js installed:</p>
  <pre><code class="language-bash">npm install -g opencode-ai
opencode --version</code></pre>
  <p>Once installed, stay in the terminal. We will configure OpenCode in Objective 3.</p>
</section>

<section class="lab-harness-branch" id="kilocode-install" data-harness-branch="kilocode">
  <p class="lab-harness-branch__eyebrow">Path B</p>
  <h3>Install Kilo Code for VS Code</h3>
  <p>Kilo Code is primarily used through the editor UI. For this Linux-first lab flow, use VS Code on the student workstation and install the extension from the marketplace.</p>
  <ol>
    <li>Open <strong>VS Code</strong>.</li>
    <li>Open the <strong>Extensions</strong> view.</li>
    <li>Search for <strong>Kilo Code</strong>.</li>
    <li>Click <strong>Install</strong>.</li>
    <li>Reload VS Code if prompted.</li>
    <li>Open the project folder you want to work in before moving to Objective 3.</li>
  </ol>
  <div class="lab-callout lab-callout--info">
    <strong>Tip:</strong> Kilo Code supports several providers and local-model options. In this lab, we will use its <strong>OpenAI Compatible</strong> provider flow so it can target Open WebUI.
  </div>
</section>

<section class="lab-harness-branch" id="droid-install" data-harness-branch="droid">
  <p class="lab-harness-branch__eyebrow">Path C</p>
  <h3>Install Factory Droid</h3>
  <p>Factory's Droid harness runs in the terminal and supports BYOK custom models through Factory configuration files.</p>
  <pre><code class="language-bash">curl -fsSL https://app.factory.ai/cli | sh
droid --version</code></pre>
  <p>If the shell needs to be reloaded after install, open a fresh terminal and rerun <code>droid --version</code>.</p>
</section>

---

## Objective 3 Execute: Configure Your Harness for Open WebUI

For all three harnesses, the common backend values are:

- `Base URL` - `{{service-url:open-webui:/api}}`
- `API Key` - `YOUR_OPENWEBUI_API_KEY`
- `Model ID` - Any model ID returned by Open WebUI, such as `qwen3.5:4b`

The shared idea is simple: your harness sends requests to Open WebUI's authenticated API endpoints instead of directly to a cloud provider.

### Execute: Apply the configuration for your chosen harness

<section class="lab-harness-branch" id="opencode-config" data-harness-branch="opencode">
  <p class="lab-harness-branch__eyebrow">Path A</p>
  <h3>Configure OpenCode</h3>
  <p>OpenCode supports OpenAI-compatible providers through its JSON config. Create either a project-local <code>opencode.json</code> file or a global config under <code>~/.config/opencode/opencode.json</code>.</p>
  
  <p>It can also be easier to start opencode once, and exit with /exit.  Use the following example to help structure your opencode.json file.  
  <pre><code class="language-json">{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "openwebui": {
      "name": "Open WebUI",
      "options": {
        "baseURL": "{{service-url:open-webui:/api}}",
      },
      "models": {
        "qwen3.5:4b": {
          "name": "Qwen 3.5 4B"
        }
      }
    }
  },
  "model": "openwebui/qwen3.5:4b"
}</code></pre>
  <p>After saving the config, you can login with <code>opencode auth login: </code></p>
<figure style="text-align: center;">
  <a href="https://i.imgur.com/wLPJOpz.png" target="_blank">
    <img 
      src="https://i.imgur.com/wLPJOpz.png" 
      style="width: 50%; display: block; margin-left: auto; margin-right: auto; border: 5px solid black;">
  </a>
  <figcaption style="margin-top: 8px; font-size: 1.1em;">
    opencode auth login
  </figcaption>
</figure>
  <p>After logging in, start OpenCode from your project directory:</p>
  <pre><code class="language-bash">cd /path/to/your/project
opencode</code></pre>
</section>

<section class="lab-harness-branch" id="kilocode-config" data-harness-branch="kilocode">
  <p class="lab-harness-branch__eyebrow">Path B</p>
  <h3>Configure Kilo Code in VS Code</h3>
  <p>Kilo Code's documented workflow is provider-driven through the extension settings UI. Use the following values when creating or editing your provider profile.</p>
  <ul>
    <li><code>API Provider</code> - <code>OpenAI Compatible</code></li>
    <li><code>OpenAI Base URL</code> - <code>{{service-url:open-webui:/api}}</code></li>
    <li><code>API Key</code> - <code>YOUR_OPENWEBUI_API_KEY</code></li>
    <li><code>Model ID</code> - <code>qwen3.5:4b</code> or another model exposed by Open WebUI</li>
    <li><code>Approval Mode</code> - Leave the safer default enabled for your first run</li>
  </ul>
  <br>
  <ol>
    <li>Open the Kilo Code panel in VS Code.</li>
    <li>Open its provider or API settings.</li>
    <li>Select <strong>OpenAI Compatible</strong> as the provider.</li>
    <li>Paste in the base URL and API key values above.</li>
    <li>Pick a model ID that exists in Open WebUI.</li>
    <li>Start a new task to verify Kilo Code can connect successfully.</li>
  </ol>

  <figure style="text-align: center;">
  <a href="https://i.imgur.com/Q61IK03.png" target="_blank">
    <img 
      src="https://i.imgur.com/Q61IK03.png" 
      style="width: 50%; display: block; margin-left: auto; margin-right: auto; border: 5px solid black;">
  </a>
  <figcaption style="margin-top: 8px; font-size: 1.1em;">
    Kilo Code Settings
  </figcaption>
</figure>
<br>

<figure style="text-align: center;">
  <a href="https://i.imgur.com/vZV9qWW.png" target="_blank">
    <img 
      src="https://i.imgur.com/vZV9qWW.png" 
      style="width: 50%; display: block; margin-left: auto; margin-right: auto; border: 5px solid black;">
  </a>
  <figcaption style="margin-top: 8px; font-size: 1.1em;">
    Provider Settings
  </figcaption>
</figure>
<br>

  <div class="lab-callout lab-callout--info">
    <strong>Tip:</strong> If model discovery fails, go back to your terminal and rerun the <code>curl /api/models</code> check from Objective 1. The harness and the curl command use the same authentication path.
  </div>
</section>

<section class="lab-harness-branch" id="droid-config" data-harness-branch="droid">
  <p class="lab-harness-branch__eyebrow">Path C</p>
  <h3>Configure Factory Droid</h3>
  <p>Factory's BYOK documentation supports custom model entries in <code>~/.factory/config.json</code>. Because Open WebUI exposes a chat-completions-compatible API, use the <code>generic-chat-completion-api</code> provider type.</p>
  <pre><code class="language-json">{
  "custom_models": [
    {
      "model_display_name": "Open WebUI - Qwen 3.5 4B",
      "model": "qwen3.5:4b",
      "base_url": "{{service-url:open-webui:/api}}",
      "api_key": "YOUR_OPENWEBUI_API_KEY",
      "provider": "generic-chat-completion-api",
      "max_tokens": 4096
    }
  ]
}</code></pre>
  <p>After saving the config:</p>
  <ol>
    <li>Launch <code>droid</code>.</li>
    <li>Open the model selector with <code>/model</code>.</li>
    <li>Choose your new custom Open WebUI model entry.</li>
    <li>Start a new session in the target project directory.</li>
  </ol>
</section>

---

## Objective 4 Execute: Build a Tiny Zork Clone

At this point, all three branches reconnect. The rest of the lab is the same no matter which harness you chose.

### Execute: Start your harness session

<div class="lab-harness-chooser" aria-label="Harness launch reminders">
  <div class="lab-harness-card" data-harness-branch="opencode">
    <span class="lab-harness-card__tag">OpenCode</span>
    <strong>Terminal Session</strong>
    <span>Run <code>opencode</code> inside the project directory.</span>
  </div>
  <div class="lab-harness-card" data-harness-branch="kilocode">
    <span class="lab-harness-card__tag">Kilo Code</span>
    <strong>VS Code Task</strong>
    <span>Open the repo folder and start a new Kilo Code task from the side panel.</span>
  </div>
  <div class="lab-harness-card" data-harness-branch="droid">
    <span class="lab-harness-card__tag">Factory Droid</span>
    <strong>CLI Session</strong>
    <span>Run <code>droid</code>, type "/mission", and ensure you've selected your custom model for each phase.</span>
  </div>
</div>

### Execute: Give the harness a shared prompt

Use the following prompt as your starting task.  Ensure you are in **Plan** mode (or a Droid Mission):

```text
You are helping me build a tiny terminal adventure game in Python.

Create a Zork-style prototype with:
- at least 5 connected rooms
- movement commands like north, south, east, and west
- a simple inventory system
- one collectible key
- one locked room or door
- a short win condition

Use clean, readable Python and keep everything runnable from the terminal.
After writing the code, explain how to launch the game and what commands the player can use.
```

### Explore: Execute the result

Once your harness produces the first version, keep pushing it with follow-up prompts:

1. Ask it to add a help command.
2. Ask it to improve room descriptions.
3. Ask it to prevent impossible movement.
4. Ask it to add one extra puzzle or hidden interaction.

Alternatively, reflect if you'd instead focused on using a Spec Driven development flow.  How might the AI model perform more accurately as the requirements become more complicated?

### Checkpoint: What success can look like

Before finishing the lab, confirm that your game can:

1. Start from the terminal without errors.
2. Accept basic movement commands.
3. Let the player pick up at least one item.
4. Use that item to unlock progress.
5. Reach a clear win state.

## Conclusion

In this lab, we:

1. Generated an Open WebUI API key.
2. Installed a harness of our choice.
3. Connected that harness back to Open WebUI.
4. Used the harness to build a small but complete coding exercise.

You should now have a repeatable pattern for testing other harnesses against the same Open WebUI deployment. We've also shown how a full local stack can work, from model selection, inference, harness installation, to real coding work.
