# Project export: Agent Arcade

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: An inspectable playground where AI agents solve logic and visual puzzles.
- Devpost: https://devpost.com/software/agent-arcade
- GitHub: https://github.com/Gaurav17Joshi/AgentArcade
- Demo: https://agentarcade.onrender.com/
- Video: https://www.youtube.com/embed/JIc6IHXySzk?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — Gaurav Joshi (30 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# Agent Arcade

**An inspectable playground where AI agents solve logic and visual puzzles.**

[Live demo](https://agentarcade.onrender.com) · [Devpost project](https://devpost.com/software/agent-arcade) · [Technical report](AGENT_ARCADE_REPORT.md)

![Agent Arcade: a Sokoban board beside the live public agent journal](assets/Agent_example.png)

Agent Arcade turns puzzles into small, controlled environments for watching an agent work. Instead of showing only a final answer, it renders the puzzle board, validates each action, and records a concise public journal of observations, plans, explorer branches, helper work, action batches, token use, and estimated cost.

## What it includes

- **Logic worlds:** Sokoban, maze, and Klotski with explicit, rule-validated actions.
- **Visual worlds:** real-image Jigsaw puzzles that require visible fragment matching.
- **Computer-control worlds:** cursor-driven Klotski and Jigsaw variants that accept literal validated drags on a virtual screen.
- **Inspectable live runs:** a model can observe, make a short public plan, create focused explorer branches, write/run a restricted Python helper, commit actions, and re-observe the result.
- **Bring your own key:** visitors use their own OpenAI or Anthropic key; public deployments do not contain an author key or store visitor keys.

## Quick start

### Requirements

- Node.js **20–24**
- An Anthropic API key for live Claude puzzle runs; an OpenAI key may also be used for provider connection checks.

### Run locally

```bash
git clone https://github.com/Gaurav17Joshi/AgentArcade.git
cd AgentArcade
npm install
cp .env.example .env.local
```

Open `.env.local` in a local editor and add only the keys you intend to use:

```bash
ANTHROPIC_API_KEY=your_anthropic_key
OPENAI_API_KEY=your_openai_key
```

Then start the app:

```bash
npm start
```

Open [http://127.0.0.1:4173](http://127.0.0.1:4173). If that port is busy, run `PORT=4174 npm start` instead. Never commit `.env.local` or paste a key into chat.

### Run a live agent

1. Choose a puzzle and level from **Puzzles**.
2. Open **API keys** and add a personal key for the current browser tab, or use your local `.env.local` setup.
3. Open **Agents**, choose a provider/model and run mode, then start the agent.
4. Watch the live board and open **View trace** to inspect the complete public journal. Explorer branches appear in the Agents panel.

The hosted app at [agentarcade.onrender.com](https://agentarcade.onrender.com) uses browser-session BYOK mode. A user key is sent only for that provider request, is not saved by Agent Arcade, and is cleared when the page reloads.

## How the agent loop works

Every environment exposes a current observation, a restricted action surface, an action validator, a solved-state check, and a renderer. The runtime follows this loop:

```text
observe → publish a concise public checkpoint → optionally explore or use a helper
       → propose a short action batch → validate → animate → observe again
```

Logic puzzles use structured moves. Visual cursor puzzles render a virtual screen and accept only valid mouse drags against its coordinates. The journal is deliberately a human-readable public trace, not hidden chain-of-thought. Boards update while the agent works and are marked solved only after the environment's actual solved-state check passes.

Small Python helpers may be written in an isolated temporary workspace for calculations such as search or board verification. The restricted runner has short CPU/memory limits and no filesystem, shell, browser, network, environment-variable, or API-key access.

## Built with Codex and GPT-5.6

Agent Arcade was built end-to-end with **Codex**. Most implementation happened in a single Codex session using **GPT-5.6 Terra at Extra High reasoning**.

Codex accelerated the project from the initial product sketch through:

- designing the puzzle-environment and agent-loop contract;
- implementing the vanilla HTML/CSS/JavaScript interface and Node streaming server;
- adding Sokoban, maze, Klotski, Jigsaw, and virtual-cursor environments;
- building the public trace, explorer-branch, sandbox-helper, validation, and rendering flows;
- diagnosing real provider-run failures, testing in the browser, refining the UI, and deploying to Render;
- preparing the reports, demo video, captions, and AI-generated narration for the OpenAI Build Week submission.

GPT-5.6 Terra was used as the high-reasoning implementation partner inside Codex; the project itself remains a BYOK multi-provider playground. Anthropic currently powers the live puzzle-runner adapter, while the OpenAI connection is available for key/model validation and can be extended with a live puzzle adapter.

## Verification

Run the lightweight syntax check before making a change:

```bash
npm run check
```

For a manual end-to-end check, start the server, select a Sokoban level, add a personal Anthropic key, run an agent, and verify that the board and **View trace** journal advance together.

## Deploying

The repository contains a Render Blueprint for one HTTPS Node web service. It hosts both the browser client and Node agent server in BYOK mode; no author API key needs to be deployed. See [RENDER_DEPLOY.md](RENDER_DEPLOY.md) for the deployment flow.

## Further documentation

- [Product and technical design report](AGENT_ARCADE_REPORT.md)
- [Implementation report](IMPLEMENTATION_REPORT.html)
- [Local API-key setup and run modes](API_SETUP.md)
- [Render deployment guide](RENDER_DEPLOY.md)
- [Puzzle and asset sources](SOURCES.md)


## Detected evidence (automated analysis)

Indexed codebase: 12 recognized source files, 191 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (16 of 16)

```
.env.example
.gitignore
AGENT_ARCADE_REPORT.md
agent_python_sandbox.py
API_SETUP.md
app.js
FREE_AGENT_LOOP_REPORT.html
IMPLEMENTATION_REPORT.html
index.html
package.json
README.md
RENDER_DEPLOY.md
render.yaml
server.mjs
SOURCES.md
styles.css
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- Document setup and Codex workflow
- Clarify live agent provider support
- Clarify both BYOK providers
- Improve Fable visual jigsaw recovery
- Add practical Agent Arcade guide
- Guide Jigsaw agents row by row
- Crop Jigsaw artwork to a square
- Make Jigsaw a real visual placement task
- Follow live run journal updates
- Retry transient Anthropic agent requests
- Add deep long-horizon agent mode
- Harden live agent tool loop
- Fix hosted BYOK provider header
- Refine Sokoban warehouse artwork
- Refine virtual cursor capture
- Add BYOK hosting flow and pixel Sokoban
- Polish Sokoban and add cursor puzzle families
- Add virtual mouse puzzle controls
- Prevent live agent helper loops
- Improve agent journals and add restricted Python helpers

## Key source files (fetched from GitHub, selected and truncated for size)

### RENDER_DEPLOY.md

```markdown
# Publish Agent Arcade on Render

This repository is configured for a single Render **Web Service**: it serves the browser app and the Node agent routes from the same HTTPS URL.

## What the public site does with keys

- It is **BYOK-only**: `render.yaml` sets `BYOK_ONLY=true`.
- Do **not** add `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` to Render.
- A visitor enters a key in the **API keys** drawer. The key is held only in that page's memory, sent over HTTPS only for the visitor's provider request, and cleared on reload.
- The server does not write, log, or persist visitor keys.

## Deploy

1. Create or sign in to [Render](https://render.com/), then choose **New → Blueprint**.
2. Connect the `Gaurav17Joshi/AgentArcade` GitHub repository and select its `main` branch.
3. Render reads [`render.yaml`](render.yaml). Confirm the **Free** instance and create the service.
4. When the deploy completes, open the generated `https://…onrender.com` link.
5. Verify `https://your-service.onrender.com/api/health` returns `{"ok":true,"mode":"byok"}`.

The Free web-service option is suitable for the hackathon link. It spins down after 15 minutes idle and can take about a minute to wake up, so open the URL shortly before a judging demo. See [Render's free-tier documentation](https://render.com/docs/free).

Every later push to `main` can automatically redeploy the service after GitHub is connected.

```

### SOURCES.md

```markdown
# Asset and puzzle sources

## Jigsaw reference images

- File: `assets/cat.jpg`
- Source: [Unsplash image delivery URL](https://images.unsplash.com/photo-1518791841217-8f162f1e1131?auto=format&fit=crop&w=1200&q=85)
- File: `assets/dog.jpg` — [Golden retriever photograph, CC0](https://commons.wikimedia.org/wiki/File:Image_of_golden_retriever.jpg).
- File: `assets/mona-lisa.jpg` — [Mona Lisa by Leonardo da Vinci, public-domain reproduction](https://commons.wikimedia.org/wiki/File:Mona_Lisa.jpg).
- File: `assets/great-wave.jpg` — [The Great Wave off Kanagawa, public-domain reproduction](https://commons.wikimedia.org/wiki/File:Great_Wave_off_Kanagawa_restored.jpg).
- File: `assets/starry-night.jpg` — [The Starry Night by Vincent van Gogh, public-domain reproduction](https://commons.wikimedia.org/wiki/File:TheStarryNightByVincentVanGogh.jpg).

They are stored locally so Jigsaw works without third-party image requests and so the cursor runner can safely capture the complete visual board. The app sends the selected reference image to a visual-model provider only when the user explicitly starts a reference-enabled live run.

## Sokoban levels

- `Microban 02`, `31`, `38`, and `47` are imported from the [Microban level text in Still Yet Another Sokoban](https://github.com/davidjoffe/sokoban/blob/main/data/sokoban/levels/microban.txt), with only board-safe exterior padding added where needed. The repository README describes its default 90-level collection as public domain.
- `Reference warehouse` is an original playable interpretation inspired by the supplied `Images/Sokoban1.png` sketch, not a claim of an exact transcription.

## Klotski levels

- `Beginner 2` and the three `Advanced` boards are playable long-block interpretations of the supplied `Images/klotski3.jpeg` through `Images/klotski6.jpeg`. They preserve the requested rule: a rectangular tile slides only along its longer axis; no rotations or diagonal moves are allowed.
- The numbered-tile convention and classic 4×5 Klotski family are documented by [Klotski.org](https://www.klotski.org/). Agent Arcade deliberately uses a 6×6 long-block variant to match the supplied reference boards and make each command legible as `1R`, `2U`, and so on.

## Maze levels

- The expert maze difficulty, dense dead-end style, and 20–30 cell rectangular target sizes are based on the freely available [Maze Printables expert collection](https://mazeprintables.com/). Agent Arcade transcribes compact grid versions so they remain keyboard- and agent-playable rather than embedding the source artwork.

## Model presets

- Anthropic preset IDs and displayed families are taken from Anthropic’s [model overview](https://platform.claude.com/docs/en/about-claude/models/overview). In particular, current Claude 4.6+ aliases use dateless IDs such as `claude-sonnet-5`.
- OpenAI preset IDs are taken from the official [OpenAI model documentation](https://developers.openai.com/api/docs/models), including `gpt-5.6-sol`, `gpt-5.6-terra`, 
[truncated — 20 more characters]
```

### package.json

```
{
  "name": "agent-arcade",
  "private": true,
  "version": "1.0.0",
  "description": "Inspectable agent puzzle environments",
  "type": "module",
  "engines": {
    "node": ">=20 <25"
  },
  "scripts": {
    "start": "node server.mjs",
    "check": "node --check app.js && node --check server.mjs"
  }
}

```

### render.yaml

```yaml
services:
  - type: web
    name: agent-arcade
    runtime: node
    plan: free
    buildCommand: npm install
    startCommand: npm start
    healthCheckPath: /api/health
    envVars:
      - key: NODE_VERSION
        value: "20"
      - key: NODE_ENV
        value: production
      - key: BYOK_ONLY
        value: "true"

```

### agent_python_sandbox.py

```python
#!/usr/bin/env python3
"""Restricted algorithm helper for Agent Arcade workspaces.

This is intentionally not a general Python shell. It accepts small, pure
calculation scripts and rejects filesystem, network, process, reflection, and
private-attribute access before execution.
"""

import ast
import resource
import sys

ALLOWED_MODULES = {
    "bisect", "collections", "functools", "heapq", "itertools", "json",
    "math", "operator", "re", "statistics",
}
BANNED_NAMES = {
    "__builtins__", "__import__", "breakpoint", "compile", "delattr",
    "dir", "eval", "exec", "exit", "getattr", "globals", "help", "input",
    "locals", "open", "quit", "setattr", "type", "vars", "object", "super",
    "os", "sys", "subprocess", "socket", "pathlib", "shutil", "builtins",
    "importlib", "ctypes", "multiprocessing", "threading", "asyncio",
    "requests", "urllib", "http", "pickle", "marshal",
}


class PolicyError(Exception):
    pass


class Policy(ast.NodeVisitor):
    def visit_Import(self, node):
        for alias in node.names:
            if alias.name not in ALLOWED_MODULES:
                raise PolicyError(f"import {alias.name!r} is not allowed")
        self.generic_visit(node)

    def visit_ImportFrom(self, node):
        if node.level or node.module not in ALLOWED_MODULES:
            raise PolicyError(f"import from {node.module!r} is not allowed")
        if any(alias.name == "*" for alias in node.names):
            raise PolicyError("star imports are not allowed")
        self.generic_visit(node)

    def visit_Name(self, node):
        if node.id.startswith("__") or node.id in BANNED_NAMES:
            raise PolicyError(f"name {node.id!r} is not allowed")
        self.generic_visit(node)

    def visit_Attribute(self, node):
        if node.attr.startswith("_"):
            raise PolicyError("private attributes are not allowed")
        self.generic_visit(node)

    def visit_ClassDef(self, node):
        raise PolicyError("classes are not allowed in helper scripts")

    def visit_With(self, node):
        raise PolicyError("with blocks are not allowed in helper scripts")

    def visit_AsyncFunctionDef(self, node):
        raise PolicyError("async code is not allowed in helper scripts")

    def visit_Await(self, node):
        raise PolicyError("async code is not allowed in helper scripts")


class Output:
    def __init__(self):
        self.parts = []
        self.size = 0

    def write(self, text):
        text = str(text)
        remaining = 8000 - self.size
        if remaining <= 0:
            return
        self.parts.append(text[:remaining])
        self.size += len(text[:remaining])

    def print(self, *values, sep=" ", end="\n"):
        self.write(sep.join(str(value) for value in values) + end)

    def value(self):
        return "".join(self.parts).strip() or "(helper completed without stdout)"


def safe_import(name, globals=None, locals=None, fromlist=(), level=0):
    if level or name not in ALLOWED_MODULES:
        raise ImportError(f"module {name!r} is not available in this helper sandbox")
    return __import__(name, globals, locals, fromlist, level)


def limit_resources():
    try:
        resource.setrlimit(resource.RLIMIT_CPU, (2, 2))
        resource.setrlimit(resource.RLIMIT_AS, (96 * 1024 * 1024, 96 * 1024 * 1024))
    except (OSError, ValueError):
        pass


SAFE_BUILTINS = {
    "abs": abs, "all": all, "any": any, "bool": bool, "dict": dict,
    "enumerate": enumerate, "filter": filter, "float": float, "int": int,
    "len": len, "list": list, "map": map, "max": max, "min": min,
    "range": range, "repr": repr, "reversed": reversed, "round": round,
    "set": set, "sorted": sorted, "str": str, "sum": sum, "tuple": tuple,
    "zip": zip,
    "Exception": Exception, "ValueError": ValueError, "print": None,
    "__import__": safe_import,
}


def main():
    if len(sys.argv) != 2:
        raise SystemExit("usage: agent_python_sandbox.py helper.py")
    with open(sys.argv[1], "r", encoding="utf-8") as source_file:
        source = source_file.read()
    if len(source) > 6000:
        raise PolicyError("helper scripts are limited to 6000 characters")
    tree = ast.parse(source, filename="agent-helper.py", mode="exec")
    Policy().visit(tree)
    limit_resources()
    output = Output()
    builtins = dict(SAFE_BUILTINS)
    builtins["print"] = output.print
    namespace = {"__builtins__": builtins, "__name__": "__agent_helper__"}
    exec(compile(tree, "agent-helper.py", "exec"), namespace, namespace)
    print(output.value())


if __name__ == "__main__":
    try:
        main()
    except (PolicyError, SyntaxError, Exception) as error:
        print(f"HELPER_ERROR: {error}")
        raise SystemExit(1)

```

### IMPLEMENTATION_REPORT.html

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Agent Arcade — Implementation Report</title>
  <style>
    body{max-width:900px;margin:0 auto;padding:48px 24px 80px;background:#0c1020;color:#e8edff;font:16px/1.65 system-ui,sans-serif}h1,h2{line-height:1.15;color:#fff}h1{font-size:42px;margin-bottom:8px}h2{margin-top:42px}p,li{color:#c4cee6}code{background:#1c2742;border:1px solid #304263;border-radius:4px;padding:2px 5px}pre{overflow:auto;background:#121a30;padding:18px;border-radius:9px;border:1px solid #2b3b5c}a{color:#80e9e4}.tag{font-size:12px;letter-spacing:1px;color:#88a0d0}.ok{color:#9bf59d}table{width:100%;border-collapse:collapse}td,th{border-bottom:1px solid #2c395a;padding:10px;text-align:left}th{color:#91a3ca}
  </style>
</head>
<body>
  <p class="tag">AGENT ARCADE · IMPLEMENTATION REPORT</p>
  <h1>Agent Arcade · redesigned puzzle-first build</h1>
  <p>This report describes the runnable first build in this repository. Open <a href="index.html">index.html</a> to use it.</p>
  <h2>What was built</h2>
  <ul>
    <li>A simplified, puzzle-first interface inspired by the supplied editorial reference: a compact rounded header, a large uncluttered puzzle canvas, and colorful pill tabs.</li>
    <li>Workspace, Agents, API keys, puzzle selection, and trace inspection are on-demand drawers rather than permanent panels. The puzzle family picker now uses category tabs plus a level selector, so a 100-level collection does not become a 100-row menu.</li>
    <li>The old marketing-copy column was removed. The puzzle canvas owns the page, with a compact identity/control bar and a live agent console that shows an animated agent, elapsed time, selected model, accumulating estimated cost, changing phase, and readable milestones while a model is working.</li>
    <li>Ten Sokoban challenges: the original warm-ups, an image-inspired warehouse, and four attributed Microban boards. The hardest imported board has an 83-move verified local route.</li>
    <li>Six maze challenges, including three dense 21 × 21 expert boards, plus five numbered 6 × 6 Klotski boards inspired by the supplied images. Long tiles are explicitly labelled; commands such as <code>2D</code> are shown in the run strip, and cross-axis movement is rejected.</li>
    <li>Eight jigsaw boards from 3 × 3 through 6 × 6 visual environments. Tiles are selected and placed using a virtual-mouse-style interaction. The Agents drawer can hide or reveal the reference image.</li>
    <li>Local-only API-key configuration through <code>.env.local</code>, a small Node server, and an API status endpoint that never returns key values.</li>
  </ul>
  <h2>How the Sokoban agent works</h2>
  <p>The local demo agent receives the canonical grid state, runs breadth-first search over legal player moves and crate pushes, then visibly applies its found route one action at a time. This makes the browser demo deterministic, free to run, and testable without an API key.</p>
  <pre>observe board → find shortest legal state path → animate actions
→ record timeline/trace → check all goals → complete</pre>
  <p>For the no-key demo, “Delegate alternatives” creates visible child-workspace reports. With Claude selected, the local server instead runs a real bounded tool loop for Sokoban, Maze, and Klotski: observe board, optionally spawn an explorer, write a temporary helper artifact, and choose short batches of legal moves. The Agents drawer exposes three explicit live-run styles: <b>Free agent</b> has no route hints; <b>Action checker</b> tests only a batch the agent has proposed itself and never searches; <b>Solver aid</b> exposes a local deterministic completion route only when the agent explicitly requests it. Free agent is the default. Every batch carries a concise public <code>Reason:</code> tied to the visible board. Each action streams to the browser while the agent remains active, so it can inspect the newly visible state and continue. The browser shows only short public activity and explorer reports, not private reasoning.</p>
  <h2>Visual and interaction design</h2>
  <p>The Sokoban board uses visual crate, goal, wall, and player affordances inspired by <code>Images/Sokoban1.png</code>, without copying its image content. The Klotski board uses a wood, grid, highlighted target, and exit treatment inspired by <code>Images/Klotski1.jpeg</code> and <code>Images/Klotski2.jpeg</code>.</p>
  <h2>Run it locally</h2>
  <pre>cd /Users/gauravjoshi/Desktop/R/AgentArcade
node server.mjs
# open http://127.0.0.1:4173</pre>
  <p>No package installation is needed. See <a href="API_SETUP.md">API_SETUP.md</a> to add your own local OpenAI and Anthropic keys without sharing them in chat.</p>
  <h2>Live provider and jigsaw checks</h2>
  <p>The current curated Anthropic preset is <code>claude-sonnet-5</code>; the dated string <code>claude-sonnet-5-20251001</code> is diagnosed as invalid. Sonnet 5 was tested in-browser on Two Crates: it observed, used the then-available route helper, stated an observable reason, streamed a three-move final batch, and solved in seven verified moves for an estimated $0.0141. Claude Haiku was also tested through the live Maze and Klotski streams: Maze solved in a reasoned 4-action batch and Klotski solved a numbered 6-action route in two batches. A live 5 × 5 cat jigsaw call produced a visual inventory (eyes, ears, chest, foreground), a harness note, and five five-tile placement batches for an estimated $0.0025—instead of a bare tile-number dump. The cat asset and level provenance are recorded in <a href="SOURCES.md">SOURCES.md</a>.</p>
  <h2>Known next runtime layer</h2>
  <table><tr><th>Included now</th><th>Next integration</th></tr><tr><td class="ok">Playable puzzle engines, compact level picker, ten Sokoban boards, expert mazes, and five Klotski boards</td><td>OpenAI puzzle runner using the Agents SDK/custom model adapter</td></tr><tr><td class="ok">C
[truncated — 1193 more characters]
```

### index.html

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <meta name="theme-color" content="#f5f0e3" />
  <title>Agent Arcade</title>
  <link rel="stylesheet" href="styles.css" />
</head>
<body>
  <main class="site-shell">
    <header class="topbar">
      <a class="brand" href="#top"><span class="brand-dots"><i></i><i></i><i></i><i></i></span><span>Agent<br><b>Arcade</b></span></a>
      <nav class="top-tabs" aria-label="Agent Arcade controls">
        <button class="nav-pill active" data-open-palette="puzzles">Puzzles</button>
        <button class="nav-pill" id="agents-tab">Agents <span id="agent-count">1</span></button>
        <button class="nav-pill sun" id="api-tab">API keys</button>
      </nav>
      <button class="workspace-tab" id="workspace-tab"><span class="online-dot"></span><span id="active-workspace">Main agent</span>⌄</button>
    </header>

    <section class="hero arena" id="top">
      <div class="arena-bar">
        <div class="arena-identity"><p class="kicker" id="puzzle-kicker">LOGIC LAB · LEVEL 02</p><b id="arena-name">Sokoban · Two crates</b><small id="mode-copy">Structured logic</small></div>
        <div class="arena-actions"><button class="black-button" id="run-button">Run agent <b>↗</b></button><button class="line-button" id="reset-button">Reset</button><button id="solution-button" class="line-button solution-button" hidden>Solution figure</button><button id="level-button" class="level-button">Levels</button><button id="undo-piece-button" class="line-button jigsaw-only" hidden>Undo tile</button></div>
      </div>
      <div class="arena-board">
        <div id="game-stage" class="game-stage" tabindex="0" aria-label="Playable puzzle"></div>
        <div id="game-caption" class="game-caption">Use arrows to move. Push every crate onto a target.</div>
      </div>
      <section id="live-run-panel" class="live-run-panel" aria-live="polite" hidden>
        <div class="agent-orb" aria-hidden="true"><i></i><i></i><b></b></div>
        <div class="live-run-copy"><p id="live-run-kicker">AGENT RUN</p><strong id="live-run-title">Agent is preparing the puzzle.</strong><span id="live-run-detail">Starting a secure workspace…</span></div>
        <div class="live-run-time"><b id="live-run-elapsed">00:00</b><span>elapsed · <em id="live-run-cost">cost pending</em></span></div>
        <ol id="live-run-events" class="live-run-events"></ol>
      </section>
    </section>

    <section class="run-strip">
      <div><b id="step-count">00</b><span>moves</span></div><div><b id="push-count">00</b><span id="metric-two">pushes</span></div><div><b id="run-status">READY</b><span>agent status</span></div><div><b id="run-cost">—</b><span>estimated api cost</span></div>
      <div class="command-readout"><span>LAST COMMAND</span><b id="command-value">—</b></div>
      <button class="plain-button" id="trace-button">View trace ↗</button>
    </section>

    <section class="library-section" aria-labelledby="how-it-works-title"><div class="section-copy"><p class="kicker">HOW AGENT ARCADE WORKS</p><h2 id="how-it-works-title">Give an agent<br>a puzzle to solve.</h2><p>Agent Arcade is a set of inspectable logic and visual puzzle environments. The model sees the selected board, works through it in a private workspace, and publishes a readable record of the decisions that change the puzzle.</p><ol class="use-steps"><li><b>1 · Choose a puzzle</b><span>Select a family and level from the six cards beside this guide.</span></li><li><b>2 · Add your own API key</b><span>Open <strong>API keys</strong> in the top bar and add an Anthropic and/or OpenAI key. Keys stay only in this browser tab. Anthropic powers the live puzzle agents; OpenAI is available for a session-key check.</span></li><li><b>3 · Start and inspect</b><span>Open <strong>Agents</strong>, choose an Anthropic model, and press <strong>Run this agent</strong>. Explorer branches appear in that panel and under <strong>Main agent</strong>; use <strong>View trace</strong> for the public run journal.</span></li></ol></div><div id="puzzle-cards" class="puzzle-cards" aria-label="Puzzle families"></div></section>
  </main>

  <aside class="drawer" id="drawer" aria-hidden="true"><div class="drawer-head"><p class="kicker" id="drawer-kicker">AGENT WORKSPACES</p><button class="close-button" data-close aria-label="Close panel">×</button></div><div id="drawer-content"></div></aside><div class="scrim" id="scrim" data-close></div>

  <template id="agents-template"><h2>Run the team.</h2><p class="drawer-intro">The main agent owns this board. It chooses whether to create explorers, can write and run restricted algorithm helpers inside its isolated workspace, and sends an operational journal while it works.</p><div class="agent-settings provider-settings"><label>PROVIDER<select id="model-select"><option value="local">Local deterministic demo · no API</option><option value="claude">Anthropic · live puzzle agent</option><option value="openai">OpenAI · key/model check</option></select></label><label id="model-preset-label">CURATED MODEL<select id="model-preset"></select></label></div><label class="custom-model-label" id="custom-model-label">CUSTOM MODEL ID <input id="model-id" spellcheck="false" autocomplete="off" placeholder="or enter a provider model ID" /></label><p class="model-help" id="model-help"></p><label class="run-mode-label">RUN STYLE<select id="run-mode"><option value="free">Free agent · no route hints</option><option value="verify">Action checker · test its own proposals</option><option value="assist">Solver aid · route only on request</option><option value="deep">Deep agent · long-horizon local run</option></select><small id="run-mode-help"></small></label><div class="team-note"><span class="team-dot"></span><span><b>Agent-directed team</b><small>It can inspect, branch into focused explorers, draft and run a restricted Python helper, then continue from the visible bo
[truncated — 3408 more characters]
```

### FREE_AGENT_LOOP_REPORT.html

```html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>Agent Arcade — Free Agent Loop</title>
  <style>
    :root{--ink:#11110f;--paper:#faf8f1;--cream:#f3eddd;--orange:#ff5a17;--blue:#3288d6;--green:#19ae58;--yellow:#f8c500;--line:#d9d2c1;--muted:#66635b}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:Arial,Helvetica,sans-serif;line-height:1.48}.page{max-width:1100px;margin:auto;padding:34px 22px 70px}header{padding:36px;border-radius:26px;background:linear-gradient(135deg,#ff5a17,#f8c500);margin-bottom:28px}header p{margin:0 0 11px;font-size:12px;font-weight:bold;letter-spacing:1px;text-transform:uppercase}h1{max-width:750px;margin:0;font-size:clamp(42px,8vw,86px);letter-spacing:-5px;line-height:.87}h2{margin:46px 0 14px;font-size:29px;line-height:1;letter-spacing:-1.5px}h3{margin:0 0 7px;font-size:17px}p{max-width:820px}code{padding:2px 5px;border-radius:4px;background:#e9e3d5;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.88em}.lede{max-width:790px;font-size:19px}.notice{margin:22px 0;padding:16px 18px;border-left:6px solid var(--orange);border-radius:0 12px 12px 0;background:#fff0e8}.flow{display:grid;grid-template-columns:repeat(5,minmax(120px,1fr));gap:10px;align-items:stretch;margin:25px 0}.node{position:relative;padding:16px;border:2px solid var(--ink);border-radius:15px;background:#fff;min-height:128px}.node:after{content:'→';position:absolute;right:-18px;top:45%;z-index:2;display:grid;place-items:center;width:25px;height:25px;border-radius:50%;background:var(--ink);color:#fff}.node:last-child:after{display:none}.node:nth-child(2){background:#dcecff}.node:nth-child(3){background:#fff1d6}.node:nth-child(4){background:#dff4e5}.node:nth-child(5){background:#fff}.node b{display:block;font-size:11px;letter-spacing:1px;text-transform:uppercase}.node span{display:block;margin-top:8px;font-size:13px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}.card{padding:22px;border:1px solid var(--line);border-radius:17px;background:#fff}.card strong{font-size:12px;letter-spacing:1px;text-transform:uppercase}.card p{margin-bottom:0;font-size:14px}.steps{display:grid;gap:0;margin:24px 0;border:1px solid var(--line);border-radius:16px;overflow:hidden;background:#fff}.step{display:grid;grid-template-columns:58px 1fr;gap:15px;padding:18px 20px;border-bottom:1px solid var(--line)}.step:last-child{border-bottom:0}.number{display:grid;place-items:center;width:36px;height:36px;border-radius:50%;background:var(--blue);color:white;font-weight:bold}.step:nth-child(2) .number{background:var(--orange)}.step:nth-child(3) .number{background:var(--yellow);color:#111}.step:nth-child(4) .number{background:var(--green)}.step p{margin:4px 0 0;font-size:14px}.tool-table{width:100%;border-collapse:collapse;margin:18px 0;background:#fff;border:1px solid var(--line);border-radius:14px;overflow:hidden}.tool-table th,.tool-table td{padding:13px;text-align:left;vertical-align:top;border-bottom:1px solid var(--line)}.tool-table tr:last-child td{border-bottom:0}.tool-table th{font-size:11px;letter-spacing:1px;text-transform:uppercase;background:var(--cream)}.yes{color:#137542;font-weight:bold}.no{color:#b13e1f;font-weight:bold}.event{display:grid;grid-template-columns:145px 1fr;gap:12px;padding:10px 0;border-bottom:1px solid var(--line)}.event:last-child{border-bottom:0}.event b{font-size:12px;letter-spacing:.6px}.boundary{margin:22px 0;padding:22px;border:2px dashed #716c60;border-radius:19px;background:#f5f0e3}.boundary .grid{margin-top:15px}.danger{padding:17px;border-radius:13px;background:#f5d7d0}.good{padding:17px;border-radius:13px;background:#dff4e5}.small{font-size:13px;color:var(--muted)}@media(max-width:800px){.flow{grid-template-columns:1fr}.node:after{content:'↓';right:auto;top:auto;bottom:-19px;left:calc(50% - 12px)}.grid{grid-template-columns:1fr}.step{grid-template-columns:42px 1fr}.event{grid-template-columns:1fr;gap:2px}}
  </style>
</head>
<body>
  <main class="page">
    <header>
      <p>Agent Arcade · Technical explainer</p>
      <h1>What a Free agent actually does</h1>
    </header>

    <p class="lede">Free agent is the honest, no-route-hints logic-puzzle mode. Claude receives a text representation of the current puzzle and a small set of safe tools. It can reason, delegate to LLM explorers, and write and run a small restricted Python calculation in a temporary workspace, but it cannot call the local solver, browse your computer, run shell commands, or access API keys.</p>

    <div class="notice"><b>The important distinction:</b> the puzzle's ordinary rules are still enforced. “Free” means no strategic route assistance, not that the agent can walk through walls, alter the level, or bypass the game interface.</div>

    <h2>One run, end to end</h2>
    <section class="flow" aria-label="Free agent request flow">
      <div class="node"><b>1 · Browser</b><span>You choose a level, model, and <em>Free agent</em>.</span></div>
      <div class="node"><b>2 · Local server</b><span>It creates the canonical in-memory puzzle state and holds the API key locally.</span></div>
      <div class="node"><b>3 · Claude</b><span>It gets a tool contract plus observations; it never receives your key.</span></div>
      <div class="node"><b>4 · Validated event</b><span>The server checks each proposed action against the real puzzle rules.</span></div>
      <div class="node"><b>5 · Render</b><span>The browser animates only committed legal actions and updates the trace.</span></div>
    </section>

    <h2>Step-by-step loop</h2>
    <section class="steps">
      <div class="step"><div class="number">1</div><div><h3>Load a canonical board</h3><p><code>app.js</code> loads the selected level from its local puzzle catalogue. For Sokoban, a compact text map defines walls (<code>#</code>), player (<code>@</code>), crates (<code>$</code>), and target
[truncated — 9353 more characters]
```

### styles.css

```css
:root{--ink:#10100d;--cream:#f5f0e3;--paper:#faf8f1;--orange:#ff5a17;--blue:#3288d6;--yellow:#f8c500;--green:#19ae58;--pink:#b776d7;--line:#ddd7c8;--muted:#68665e}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:Arial,Helvetica,sans-serif}.site-shell{padding:7px;max-width:1600px;margin:auto}.topbar{height:68px;display:grid;grid-template-columns:220px 1fr 190px;align-items:center;padding:0 13px;border-radius:27px 27px 0 0;background:var(--cream);position:relative;z-index:2}.brand{display:flex;align-items:center;gap:9px;color:var(--ink);text-decoration:none;font-weight:700;font-size:14px;line-height:.72;text-transform:uppercase;letter-spacing:-.4px}.brand b{font-size:18px}.brand-dots{display:flex;gap:2px}.brand-dots i{display:grid;place-items:center;width:30px;height:30px;border-radius:50%;background:var(--blue);border:2px solid #111;transform:rotate(-20deg)}.brand-dots i:after{content:'↗';font-size:14px}.brand-dots i:nth-child(2){background:var(--orange)}.brand-dots i:nth-child(3){background:var(--yellow)}.brand-dots i:nth-child(4){background:var(--green)}.top-tabs{display:flex;justify-content:center;gap:8px}.nav-pill,.workspace-tab,.line-button,.plain-button,.small-button{font-weight:700;border:0;cursor:pointer;background:none}.nav-pill{min-width:112px;padding:11px 17px;border-radius:999px;text-transform:uppercase;font-size:11px;background:var(--blue)}.nav-pill.active{background:var(--orange)}.nav-pill.sun{background:var(--yellow)}.nav-pill span{background:#111;color:#fff;border-radius:50%;padding:2px 5px;margin-left:4px}.workspace-tab{justify-self:end;font-size:12px;display:flex;gap:7px;align-items:center}.online-dot{width:8px;height:8px;border-radius:50%;background:var(--green)}.hero{min-height:570px;display:grid;grid-template-columns:1fr 1fr;background:var(--cream);border-radius:0 0 26px 26px;overflow:hidden}.hero-copy{padding:clamp(45px,8vw,130px) clamp(28px,6vw,100px);background:linear-gradient(145deg,#dfc4aa,#a87557);color:#fff;position:relative}.hero-copy:after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 40% 20%,#fff4 0 1px,transparent 2px);background-size:14px 14px;mix-blend-mode:soft-light;opacity:.4}.hero-copy>*{position:relative;z-index:1}.kicker{font-size:10px;font-weight:700;letter-spacing:1px;margin:0 0 14px;text-transform:uppercase}.hero h1{font-size:clamp(43px,6vw,81px);line-height:.89;letter-spacing:-5px;margin:0 0 20px}.hero-copy>p:not(.kicker){max-width:330px;font-size:15px;line-height:1.25}.hero-actions{display:flex;gap:9px;margin-top:28px}.black-button,.line-button{padding:13px 18px;border-radius:999px;font-size:12px;font-weight:700;text-decoration:none;display:inline-block}.black-button{background:#111;color:#fff;border:0;cursor:pointer}.line-button{border:1px solid #fff8;color:#fff}.hero-art{position:relative;display:grid;place-items:center;padding:45px;background:#f8f5e9}.art-corner{position:absolute;top:25px;right:28px;font-size:11px;font-weight:700;text-align:right}.art-corner small{font-weight:400;color:var(--muted)}.game-stage{width:min(81%,560px);min-height:390px;display:grid;place-items:center;outline:none}.game-caption{position:absolute;bottom:25px;left:28px;max-width:245px;font-size:11px;line-height:1.2}.run-strip{display:flex;align-items:center;gap:30px;padding:21px 2px;border-bottom:1px solid var(--line)}.run-strip>div:not(.command-readout){display:grid;gap:2px}.run-strip b{font-size:17px}.run-strip span{font-size:10px;color:var(--muted);text-transform:uppercase;letter-spacing:.6px}.command-readout{margin-left:auto;display:grid;gap:3px;text-align:right}.command-readout b{font-size:16px}.plain-button{font-size:12px;padding:9px}.library-section{display:grid;grid-template-columns:280px 1fr;gap:40px;padding:70px 2px}.section-copy h2{font-size:42px;letter-spacing:-2.5px;line-height:.9;margin:0 0 15px}.section-copy>p:not(.kicker){font-size:13px;line-height:1.4;color:var(--muted)}.puzzle-cards{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.puzzle-card{min-height:220px;text-align:left;border:0;border-radius:20px;padding:20px;cursor:pointer;position:relative;overflow:hidden;background:#e2d7ca}.puzzle-card:nth-child(2){background:#cddcf0}.puzzle-card:nth-child(3){background:#efd289}.puzzle-card:nth-child(4){background:#dabde8}.puzzle-card h3{font-size:26px;letter-spacing:-1.2px;margin:5px 0}.puzzle-card p{font-size:11px;width:62%;line-height:1.25}.puzzle-card b{position:absolute;right:20px;bottom:17px;font-size:26px}.puzzle-card.active:after{content:'ACTIVE';position:absolute;top:15px;right:15px;font-size:9px;letter-spacing:1px;font-weight:bold}.drawer{position:fixed;z-index:20;right:15px;top:15px;bottom:15px;width:min(410px,calc(100vw - 30px));padding:26px;background:#fffdf7;box-shadow:-20px 0 60px #0003;transform:translateX(calc(100% + 30px));transition:transform .3s ease;overflow:auto}.drawer.show{transform:translateX(0)}.scrim{position:fixed;z-index:19;inset:0;background:#0004;opacity:0;pointer-events:none;transition:.25s}.scrim.show{opacity:1;pointer-events:auto}.drawer-head{display:flex;justify-content:space-between;align-items:start}.close-button{font-size:26px;border:0;background:none;cursor:pointer;line-height:.6}.drawer h2{font-size:38px;line-height:.9;letter-spacing:-2.2px;margin:25px 0 12px}.drawer-intro{font-size:13px;line-height:1.35;color:var(--muted);max-width:340px}.agent-settings{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin:24px 0}.agent-settings label{display:grid;gap:5px;font-size:9px;font-weight:bold;letter-spacing:.8px}.agent-settings select{padding:9px;border:1px solid var(--line);background:#fff;font-size:11px}.wide{width:100%;text-align:center}.divider{height:1px;background:var(--line);margin:25px 0}.drawer-row{display:flex;justify-content:space-between;align-items:center}.small-button{padding:8px 11px;background:var(--yellow);border-radius:99px;font-size:11px}.agent-list,.trace-list,.full-trace,.workspace-list,.drawer-puzzle-list{di
[truncated — 31474 more characters]
```

[2 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]