# Project export: Hackathon Roblox AI Agent

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: Built by two young brothers, our AI agent turns kids’ ideas into playable Roblox games, helping Future Tech Foundation (Our NPO) empower children to create, not just consume, technology.
- Devpost: https://devpost.com/software/hackathon-roblox-ai-agent
- GitHub: https://github.com/awantony3/Roblox-Hackathon-Agent
- Video: https://www.youtube.com/embed/r8fU4UqmGLc?enablejsapi=1&hl=en_US&rel=0&start=1&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — awantony3 (8 commits)

## Devpost submission (written by the team)

### Inspiration

The idea behind Hackathon Roblox AI Agent was born due to the idea of our Future Tech Foundation which is a non-profit making organization run by myself and my younger brother who was then aged 13 and 12 respectively. The idea has been conceived due to the reason that there are quite a few children who have an idea of the game but lack the programming skills or whatever is needed to develop the game. The children just waste a lot of time playing someone else's world; however, when they try developing a world in Roblox studio, nothing happens. With the help of our agent, the child can describe his/her game in natural language, see it develop in Roblox Studio, play it and even ask for some changes in it.

### What it does

The Hackathon Roblox AI Agent turns a child’s game idea into a playable Roblox experience. A creator describes what they want, and the agent reads the live Roblox Studio project, understands its structure, creates a plan, and begins building. It can generate environments, mechanics, user interfaces, assets, and Luau scripts. It then tests the result, identifies problems, and improves the game instead of assuming the first attempt worked. The agent follows a ReAct-style cycle: reason, act, observe, and decide what to do next. It also uses a Reflexion-inspired process to study failed attempts and turn them into guidance for the next iteration. The result is more than prompt-to-code generation. The agent translates a child’s intent into a tested experience they can play, evaluate, and continue shaping.

### How we built it

The agent was implemented using Codex, a TypeScript and Express backend, and a safe Roblox Studio plugin. Codex implements planning, reasoning, tool selection, and coding choices. The backend manages the coordination of the process and provides 26 typed capabilities for inspecting projects, making instances, modifying properties, scripting in Luau, managing assets, and testing. The Roblox Studio plugin transforms the tool calls made by the agent into safe actions on the live project. It allows the creator to work in the Roblox Studio while enabling the backend to manage the agent. Our system constantly analyzes, plans, constructs, tests, reflects, and iterates. We have also developed 16 automated tests that verify syntax, asset loading, game initialization, movement, user interface, mechanics, performance, and win condition. In order to enhance security and reliability of our system, we have implemented confirmations for high impact actions, timeouts, rollback, quarantine of assets, and processing of animations and Meshy 3D asset pipeline.

### Challenges we ran into

However, the most important problem that we have encountered was the problem of reliability. There are many interconnected subsystems in the game created in Roblox: scripts, physics, assets, UI, player interaction, goals and victory conditions. Even though the code is syntactically correct, it may fail because of lack of particular elements, wrong order of construction, mechanisms conflicts and so on. The way to deal with the issue was letting the agent watch the evidence rather than believe in the produced code. With the help of the ReAct cycle, the agent will perform the action, analyze its result and change its strategy accordingly. With the help of Reflexion, the agent will analyze the errors that were made during the process and consider them for the next attempt. Moreover, we had to get the balance between autonomy, safety and simplicity. The agent should be autonomous enough to be able to produce the games. At the same time, it should remain clear to the beginners.

### Accomplishments we're proud of

Wear our proud of our agent does not stop with the creation of the code. The prototype can perform analysis of the project in Roblox Studio, its understanding in terms of its composition, changes required, creation of the environment and game logic, generation of Luau scripts, testing the experience, detection of errors, fixing of the error, and validation of the results. Our technology employs 26 typed utilities, 16 automated tests, runtime verification, reflexion-based re-executions, rollbacks, timeouts, confirmation controls, asset quarantine, animation capability, and 3D asset management. The greatest success of our work in this project is the realization of our concept until playing experience level. Even a beginner developer can move from the idea to the game without having any experience in programming. For the perspective of Future Tech Foundation, the prototype demonstrates how powerful AI can be the creative tool available to kids with great ideas.As far as the Future Tech Foundation is concerned, our prototype shows that the AI can become a helpful tool to implement kids' ideas in their creative process.

### What we learned

The truth was revealed that an effective AI developer does not need only to program. It has to be able to analyze the situation, develop a strategy, act purposefully, evaluate its result, realize the failure, and transform itself in order to apply a new strategy if the old one fails. The experience with ReAct helped us see how the process of game development can be split into several interrelated stages interacting with each other through the feedback received from Roblox Studio. The Reflexion made us understand that a failure can be a valuable source of data for the next stage. We got one more interesting piece of knowledge from this practice. We learned that access of children to the Future Tech Foundation is not only a matter of giving kids appropriate software. It must be safe, understandable, tolerant, and developed in a beginner language. An AI agent cannot replace the creativity of children or cover the process of learning. On the contrary, it has to be transparent, assist kids in decision-making, and encourage their desire to learn and create.

### What's next

The second objective is connected to the idea of developing a visual and creative educational agent. It is supposed to improve the ability to analyze screenshots, evaluate UI, observe the behavior of NPCs, create environment, produce higher quality animations, and work with complicated 3D models. In addition, the agent has to back up its propositions and changes in terms of language that would be clear for young creators. Children must remain active participants making decisions and not passive users of the automatic system. Within the framework of the Future Tech Foundation, we are going to use this instrument in workshops, receive the feedback of young creators, make the connection between the students and their mentors, equip them with necessary tools and access to competitions. The Foundation is going to supply us with the human factor that we cannot get from the software.Therefore, the final objective of our project will not be limited by acceleration of the process of Roblox creation. The long-term objective of our project will also include the help of children in becoming game creators themselves rather than players of other people's games.

## README (from the GitHub repository)

# Roblox Hackathon Agent

An OpenAI-powered Roblox Studio game-building agent that turns a natural-language goal into inspected, implemented, and verified changes inside an open Roblox project.

GPT-5.6 powers the agent's planning, tool selection, final responses, and evidence-grounded Reflexion retries through the official OpenAI SDK. Typed tools connect the model to a queued HTTP bridge and a Roblox Studio plugin, while human approval protects destructive actions. ChatGPT and OpenAI Codex also helped design, implement, test, and document the project.

> **Runtime:** OpenAI GPT-5.6 via the Chat Completions API and function calling. Set `OPENAI_MODEL` to use another compatible OpenAI model.

## What it can do

- Inspect the Explorer hierarchy, selection, scripts, properties, camera state, and recent output.
- Create and modify Roblox instances and Luau scripts.
- Build many related objects from one declarative specification.
- Generate TweenService scripts, KeyframeSequences, animation previews, and animation configurations.
- Search Creator Store models, quarantine them, scan their scripts, and remove scripts by default before insertion.
- Start Meshy text-to-3D jobs and download validated GLB or FBX artifacts.
- Observe Play mode, runtime errors, output, player state, and typed assertions.
- Evaluate whether an attempt produced verified evidence instead of trusting the model's final message.
- Reflect on failed or inconclusive attempts and retry with a revised strategy.

The agent is genre-neutral. Its behavior comes from the user's goal and the systems it generates rather than a hard-coded game template.

## Agent structure

```text
User request
    │
    ▼
Express API server
    │ creates a task
    ▼
ReActAgent ⇄ OpenAI GPT-5.6
    │          plans, selects functions, and reads observations
    ▼
Typed tool registry
    │
    ├──► Backend integrations such as optional Meshy 3D generation
    │
    └──► StudioBridge command queue
              │
              ▼
        Roblox Studio plugin
              │ inspects or changes the open project
              ▼
        Structured tool result
              │
              └──────────────► ReActAgent

Completed attempt
    │
    ▼
OutcomeEvaluator
    ├── passed ─────────────────────────────► Complete
    └── failed or inconclusive ─────────────► OpenAI-powered Reflexion
                                                   │ revised strategy
                                                   └────────► Next ReAct attempt
```

### 1. API and task orchestration

`backend/src/server.ts` is the backend entry point. It exposes endpoints for health, readiness, Studio sessions, queued commands, command results, and agent tasks.

When the plugin submits a user goal, the server:

1. Confirms that the model credentials exist.
2. Confirms that the requested Studio session is connected.
3. Creates an asynchronous task.
4. Starts a new `ReActAgent`.
5. Records state events such as `planning`, `acting`, `observing`, `evaluating`, and `reflecting`.
6. Returns a task ID that the plugin can poll for progress and results.

The API is protected by `AGENT_API_KEY`, except for the public health endpoint.

### 2. ReAct reasoning loop

`backend/src/agent/ReActAgent.ts` controls the main agent loop. `OpenAIClient` initializes the official OpenAI SDK with `OPENAI_API_KEY`. The ReAct agent calls the OpenAI Chat Completions API with the configured model and exposes the project tool registry as function definitions.

For each attempt, the agent:

1. Sends the system instructions, user goal, and tool definitions to the model.
2. Receives one or more typed tool calls.
3. Executes those calls and records every input and observation in a trace.
4. Returns each structured result to the model.
5. Continues until the model stops calling tools or the iteration budget is exhausted.

The default budget is 20 ReAct iterations per attempt and up to two Reflexion retries. These values can be changed with `MAX_REACT_ITERATIONS` and `MAX_REFLEXION_ATTEMPTS`.

### 3. Typed tool registry

`backend/src/tools/registry.ts` defines every action available to the model, including its name, description, JSON input schema, and destructive-action classification.

The tools fall into several groups:

- **Inspection:** scan the project, find instances, inspect properties, and read scripts.
- **Construction:** create instances, set properties, create scripts, and build from a specification.
- **Editing:** patch or replace scripts and delete instances.
- **Animation:** create tweens and KeyframeSequences, preview animations, and configure uploaded assets.
- **Asset workflows:** search and quarantine Creator Store assets or generate Meshy 3D assets.
- **Validation:** validate project structure, observe runtime state, and evaluate playtest assertions.

Most tools are forwarded to Roblox Studio. Optional Meshy operations execute directly in the backend; Meshy generates external 3D assets but does not power the agent's reasoning.

### 4. Queued Studio bridge

`backend/src/studio/StudioBridge.ts` connects the asynchronous model loop to Roblox Studio.

The backend cannot directly mutate a Studio project. Instead, it places each command in a session-specific queue. The Studio plugin polls for the next command, executes it locally, and posts a structured result back to the backend. The bridge resolves the waiting tool call when that result arrives.

A Studio session is considered connected only when it has checked in within the last five seconds. Commands fail if Studio does not answer before `STUDIO_COMMAND_TIMEOUT_MS`.

### 5. Roblox Studio plugin

`studio-plugin/RobloxHackathonAgent.lua` is the only component allowed to change the open Roblox project. It captures project snapshots, polls the backend, decodes typed Roblox values, runs tool handlers, and returns observations.

Mutations use Roblox Change History recordings. Successful commands are committed as undoable Studio actions, while failed commands cancel their recordings. Script replacement, exact script patches, deletion, animation publishing, and approval of quarantined assets require explicit user confirmation inside Studio.

### 6. Evidence-based evaluation

`backend/src/agent/OutcomeEvaluator.ts` decides whether an attempt actually succeeded. It does not accept the model's final prose as proof.

An attempt fails when a required tool fails. It is inconclusive when no observable action occurred, an external workflow still needs the user, or the last mutation was not followed by a successful inspection or validation tool. It passes only when the trace contains successful, verified evidence.

### 7. Reflexion and retry protection

When an attempt fails or is inconclusive, `backend/src/agent/ReflexionEngine.ts` asks the model for a structured, evidence-grounded reflection containing root causes, failed approaches, a revised strategy, and whether retrying is worthwhile.

`backend/src/agent/FailedActionGuard.ts` fingerprints failed tool calls. It blocks the same failed tool and identical input from being repeated until a successful action creates new evidence. This prevents the agent from getting stuck retrying the same command.

Validated reflections are stored by `backend/src/memory/LessonStore.ts`. A lesson is saved only when its revised attempt later succeeds, and each session retains its 20 most recent lessons.

## Repository layout

```text
backend/
├── src/
│   ├── agent/
│   │   ├── OpenAIClient.ts        # Official OpenAI runtime client
│   │   ├── ReActAgent.ts          # Main tool-use loop
│   │   ├── OutcomeEvaluator.ts    # Evidence-based success checks
│   │   ├── ReflexionEngine.ts     # Structured retry strategy
│   │   ├── FailedActionGuard.ts   # Repeated-failure protection
│   │   └── prompts.ts             # Agent operating contract
│   ├── assets/MeshyClient.ts      # External 3D generation
│   ├── memory/LessonStore.ts      # Validated session lessons
│   ├── studio/StudioBridge.ts     #

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 34 recognized source files, 174 KB.
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- OpenAI (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (40 of 40)

```
.env.example
.gitignore
backend/src/agent/FailedActionGuard.ts
backend/src/agent/OpenAIClient.ts
backend/src/agent/OutcomeEvaluator.ts
backend/src/agent/prompts.ts
backend/src/agent/ReActAgent.ts
backend/src/agent/ReflexionEngine.ts
backend/src/assets/MeshyClient.ts
backend/src/config.ts
backend/src/doctor.ts
backend/src/memory/LessonStore.ts
backend/src/readiness.ts
backend/src/server.ts
backend/src/studio/StudioBridge.ts
backend/src/tools/registry.ts
backend/src/types.ts
backend/tests/FailedActionGuard.test.ts
backend/tests/MeshyClient.test.ts
backend/tests/OutcomeEvaluator.test.ts
backend/tests/Readiness.test.ts
backend/tests/StudioBridge.test.ts
docs/ANIMATION_WORKFLOW.md
docs/ASSET_PIPELINES.md
docs/HACKATHON_RUNBOOK.md
docs/INTEGRATION_TEST.md
docs/RED_TEAM.md
docs/ROBLOX_STUDIO_DEMO_TEST_PLAN.md
docs/RUNTIME_TESTING.md
package.json
PLAN.md
README.md
studio-plugin/RobloxHackathonAgent.lua
studio-plugin/sync.sh
tools/build-obby.mjs
tsconfig.json
website/index.html
website/script.js
website/styles.css
website/vercel.json
```

### Dependencies

- package.json: @types/cors@^2.8.17, @types/express@^5.0.0, @types/node@^22.10.5, cors@^2.8.5, dotenv@^16.4.5, express@^4.21.2, openai@^6.48.0, tsx@^4.19.2, typescript@^5.7.2, zod@^3.24.2

### Recent commits (newest first)

- Put download and setup steps first
- Add judge download and setup website
- Align README with OpenAI runtime
- Migrate agent runtime to OpenAI
- Explain agent structure in README
- Remake README around OpenAI
- Use Codex in architecture diagram
- Document ChatGPT and Codex contributions
- Build Roblox Hackathon Agent

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

### PLAN.md

```markdown
# Roblox Hackathon Agent — Development Plan

## 1. Objective

Build an independent AI agent that can create and modify Roblox games through a Roblox Studio plugin. The agent must be genre-neutral and capable of:

- Seeing the current game hierarchy, scripts, selection, properties, and runtime evidence.
- Altering existing Roblox instances and systems.
- Writing and editing secure Luau code.
- Building environments, gameplay systems, UI, lighting, and effects.
- Creating procedural animation and using uploaded Roblox animation assets.
- Using Roblox, Toolbox, generated, and external 3D assets when configured.
- Testing its work and collecting observable evidence.
- Using ReAct to execute tasks and Reflexion to revise failed approaches.
- Applying safe changes automatically while requiring confirmation for destructive changes.

The project is separate from `/Users/antonywang/roblox-ai-assistant`. The older project is an architectural reference only.

## 2. Product principles

1. **Evidence before completion:** The agent must not report success solely because a model says it succeeded.
2. **Studio remains authoritative:** Only the installed plugin may mutate the open Roblox project.
3. **Safe autonomy:** Additive and low-risk changes may run automatically. Destructive changes require confirmation.
4. **Genre neutrality:** Tools provide general Roblox capabilities rather than hard-coded game templates.
5. **Server authority:** Rewards, damage, purchases, inventory, and saved data must be validated by server code.
6. **Structured context:** Supply project facts and tool observations instead of accumulating prompt rules.
7. **Fail closed:** Missing credentials or unsupported platform functionality must produce explicit errors.
8. **Hackathon focus:** Prioritize one polished end-to-end workflow before expanding integrations.

## 3. System architecture

```text
┌──────────────────────────────────────────────────────────────┐
│                     Roblox Studio Plugin                     │
│                                                              │
│  Agent panel → Project scanner → Command executor            │
│        │               │                 │                   │
│        │               │                 ├─ Build             │
│        │               │                 ├─ Alter             │
│        │               │                 ├─ Code              │
│        │               │                 └─ Animate           │
│        │               │                                     │
│        └──────── Destructive-change confirmation              │
└────────────────────────────┬─────────────────────────────────┘
                             │ Local HTTP command protocol
┌────────────────────────────▼─────────────────────────────────┐
│                     TypeScript Backend                       │
│                                                              │
│  API → StudioBridge → ToolRegistry → ReActAgent              │
│         
[truncated — 15089 more characters]
```

### docs/RED_TEAM.md

```markdown
# Red-team findings

- A general-purpose promise is only credible if tools are composable; no genre-specific planner is required, but every generated game still needs explicit acceptance criteria.
- Model self-evaluation is insufficient. The current evaluator trusts tool success and must be strengthened with runtime assertions.
- Destructive confirmation prevents silent deletion and replacement, but property edits can still damage behavior. A change journal is the next safety priority.
- Animation upload and external asset generation are authentication pipelines, not merely prompts. Both intentionally fail closed until configured.
- A failed tool can cause Reflexion, but repeated semantically identical actions are not yet detected.
- Batch construction is necessary for demo speed but currently needs atomic rollback.
- The plugin sends script source to the local backend and model. Users must understand that privacy boundary.
- A live demo depends on Studio HTTP settings, localhost access, API availability, and model latency. Prepare a small prebuilt place and recorded fallback.

```

### package.json

```
{
  "name": "roblox-hackathon-agent",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "tsx watch backend/src/server.ts",
    "start": "tsx backend/src/server.ts",
    "doctor": "node --import tsx backend/src/doctor.ts",
    "typecheck": "tsc --noEmit",
    "test": "tsx --test backend/tests/**/*.test.ts"
  },
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.21.2",
    "openai": "^6.48.0",
    "zod": "^3.24.2"
  },
  "devDependencies": {
    "@types/cors": "^2.8.17",
    "@types/express": "^5.0.0",
    "@types/node": "^22.10.5",
    "tsx": "^4.19.2",
    "typescript": "^5.7.2"
  }
}

```

### backend/src/server.ts

```typescript
import express from "express";
import cors from "cors";
import { randomUUID } from "node:crypto";
import { config, configurationStatus } from "./config.js";
import { studioBridge } from "./studio/StudioBridge.js";
import { ReActAgent } from "./agent/ReActAgent.js";
import { isDestructiveTool, toolNames } from "./tools/registry.js";

const app = express();
app.use(cors());
app.use(express.json({ limit: "10mb" }));
app.use((req, res, next) => {
  if (req.path === "/health" || req.header("x-api-key") === config.apiKey) return next();
  res.status(401).json({ error: "Unauthorized" });
});

const tasks = new Map<string, Record<string, unknown>>();
const MAX_TASKS = 100;

function pruneTasks() {
  while (tasks.size >= MAX_TASKS) {
    const oldest = tasks.keys().next().value as string | undefined;
    if (!oldest) break;
    tasks.delete(oldest);
  }
}

app.get("/health", (_req, res) => res.json({ ok: true, name: "roblox-hackathon-agent", model: config.model }));

app.get("/api/status", (_req, res) => {
  const sessions = studioBridge.sessions();
  const configuration = configurationStatus();
  const blockers: string[] = [];
  if (!configuration.openaiConfigured) blockers.push("OPENAI_API_KEY is not configured");
  if (!sessions.some((session) => session.connected)) blockers.push("Roblox Studio plugin is not connected");
  if (configuration.apiKeyIsDefault) blockers.push("AGENT_API_KEY is still the default value");
  res.json({
    ready: blockers.length === 0,
    blockers,
    configuration,
    studio: { connectedSessions: sessions.filter((session) => session.connected).length },
    capabilities: { toolCount: toolNames().length, tools: toolNames() },
    tasks: { retained: tasks.size, limit: MAX_TASKS }
  });
});

app.post("/api/studio/connect", (req, res) => {
  studioBridge.connect(req.body.sessionId, req.body.snapshot);
  res.json({ ok: true });
});

app.get("/api/studio/sessions", (_req, res) => {
  res.json({ sessions: studioBridge.sessions().map(({ sessionId, seenAt, connected, snapshot }) => ({
    sessionId, seenAt, connected, placeId: snapshot?.placeId, gameId: snapshot?.gameId, capturedAt: snapshot?.capturedAt
  })) });
});

app.post("/api/studio/execute", async (req, res) => {
  const { sessionId, tool, input } = req.body;
  if (!sessionId || !tool || !input || typeof input !== "object") {
    return res.status(400).json({ error: "sessionId, tool, and input object are required" });
  }
  const session = studioBridge.sessions().find((item) => item.sessionId === sessionId && item.connected);
  if (!session) return res.status(409).json({ error: "Studio session is not connected" });
  const result = await studioBridge.execute(sessionId, tool, input, isDestructiveTool(tool));
  res.status(result.success ? 200 : 422).json(result);
});

app.get("/api/studio/commands/:sessionId", (req, res) => {
  res.json({ command: studioBridge.poll(req.params.sessionId) });
});

app.post("/api/studio/results/:commandId", (req, res) => {
  res.json({ accepted: studioBridge.complete(req.params.commandId, req.body) });
});

app.post("/api/agent/tasks", (req, res) => {
  const { sessionId, message } = req.body;
  if (!sessionId || !message) return res.status(400).json({ error: "sessionId and message are required" });
  if (!config.openaiApiKey) return res.status(503).json({ error: "OPENAI_API_KEY is not configured" });
  const connected = studioBridge.sessions().some((session) => session.sessionId === sessionId && session.connected);
  if (!connected) return res.status(409).json({ error: "Requested Roblox Studio session is not connected" });
  pruneTasks();
  const taskId = randomUUID();
  tasks.set(taskId, { status: "running", state: "planning", events: [], createdAt: new Date().toISOString() });
  const agent = new ReActAgent();
  void agent.run(sessionId, message, (state, detail) => {
    const task = tasks.get(taskId)!;
    task.state = state;
    const events = task.events as unknown[];
    events.push({ state, detail, at: new Date().toISOString() });
    if (events.length > 100) events.splice(0, events.length - 100);
  }).then((result) => Object.assign(tasks.get(taskId)!, { status: result.success ? "complete" : "failed", result }))
    .catch((error: Error) => Object.assign(tasks.get(taskId)!, { status: "failed", error: error.message }));
  res.status(202).json({ taskId });
});

app.get("/api/agent/tasks/:taskId", (req, res) => {
  const task = tasks.get(req.params.taskId);
  if (!task) return res.status(404).json({ error: "Task not found" });
  res.json(task);
});

app.listen(config.port, () => console.log(`Roblox Hackathon Agent listening on http://localhost:${config.port}`));

```

### studio-plugin/sync.sh

```shell
#!/bin/bash
set -e
SOURCE="$(cd "$(dirname "$0")" && pwd)/RobloxHackathonAgent.lua"
TARGET="$HOME/Documents/Roblox/Plugins/RobloxHackathonAgent.lua"
mkdir -p "$(dirname "$TARGET")"
cp "$SOURCE" "$TARGET"
echo "Installed Roblox Hackathon Agent plugin at $TARGET"

```

### website/script.js

```javascript
const toast = document.querySelector(".toast");
let toastTimer;

document.querySelectorAll("[data-copy]").forEach((button) => {
  button.addEventListener("click", async () => {
    const value = button.dataset.copy;
    try {
      await navigator.clipboard.writeText(value);
      const original = button.textContent;
      button.textContent = "Copied";
      toast.classList.add("show");
      clearTimeout(toastTimer);
      toastTimer = setTimeout(() => toast.classList.remove("show"), 1600);
      setTimeout(() => { button.textContent = original; }, 1600);
    } catch {
      window.prompt("Copy this text:", value);
    }
  });
});

```

### website/index.html

```html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta name="description" content="Download, install, and test the OpenAI-powered Roblox Hackathon Agent." />
    <title>Roblox Hackathon Agent — Judge Setup</title>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div class="noise" aria-hidden="true"></div>
    <header class="site-header">
      <a class="brand" href="#top" aria-label="Roblox Hackathon Agent home">
        <span class="brand-mark" aria-hidden="true"><span></span></span>
        <span>ROBLOX<span class="brand-muted">/AGENT</span></span>
      </a>
      <nav aria-label="Main navigation">
        <a href="#setup">Full instructions</a>
        <a href="https://github.com/awantony3/Roblox-Hackathon-Agent" target="_blank" rel="noreferrer">Source ↗</a>
        <a class="nav-download" href="https://github.com/awantony3/Roblox-Hackathon-Agent/archive/refs/heads/main.zip">Download ZIP ↓</a>
      </nav>
    </header>

    <main id="top">
      <section class="hero">
        <div class="hero-copy">
          <div class="eyebrow"><span class="status-dot"></span> Judge-ready setup guide</div>
          <h1>Download the agent.<br /><span>Follow the steps.</span></h1>
          <p class="hero-lede">Everything judges need is on this page: the complete agent download, required credentials, exact commands, Roblox Studio setup, and a test prompt.</p>
          <div class="hero-actions">
            <a class="button button-primary download-main" href="https://github.com/awantony3/Roblox-Hackathon-Agent/archive/refs/heads/main.zip" download>
              <span>Download complete agent (.zip)</span><span aria-hidden="true">↓</span>
            </a>
            <a class="button button-secondary" href="#setup">Open detailed instructions <span aria-hidden="true">↓</span></a>
          </div>
          <p class="download-note">Roblox-Hackathon-Agent-main.zip · Complete source · Latest main branch</p>
        </div>

        <div class="start-card" aria-label="Exact setup steps">
          <div class="start-card-header">
            <span>EXACT SETUP — 8 STEPS</span>
            <span>≈ 10 MIN</span>
          </div>
          <ol class="start-list">
            <li><span>1</span><div><strong>Download</strong><small>Get the complete ZIP above</small></div></li>
            <li><span>2</span><div><strong>Extract</strong><small>Open the downloaded ZIP</small></div></li>
            <li><span>3</span><div><strong>Open Terminal</strong><small>Run commands inside the folder</small></div></li>
            <li><span>4</span><div><strong>Add credentials</strong><small>Set OPENAI_API_KEY in .env</small></div></li>
            <li><span>5</span><div><strong>Install</strong><small>Run npm install and checks</small></div></li>
            <li><span>6</span><div><strong>Install plugin</strong><small>Run studio-plugin/sync.sh</small></div></li>
            <li><span>7</span><div><strong>Start agent</strong><small>Run npm start</small></div></li>
            <li><span>8</span><div><strong>Test in Studio</strong><small>Enable HTTP and run the prompt</small></div></li>
          </ol>
          <a class="start-card-link" href="#setup">See commands and screenshots guidance ↓</a>
        </div>
      </section>

      <section class="quick-facts" aria-label="Project facts">
        <div><span>01</span><p><strong>OpenAI-powered</strong><br />ReAct + Reflexion runtime</p></div>
        <div><span>02</span><p><strong>Studio-native</strong><br />Typed, undoable mutations</p></div>
        <div><span>03</span><p><strong>Evidence-first</strong><br />Verifies before completion</p></div>
      </section>

      <section class="section setup-section" id="setup">
        <div class="section-heading">
          <div><span class="section-number">01</span><p class="kicker">Installation</p></div>
          <h2>From ZIP to running agent.</h2>
          <p>Complete these steps in order. Every command can be copied with one click.</p>
        </div>

        <div class="requirements">
          <div class="requirement-title">Before you begin</div>
          <ul>
            <li><span>✓</span> macOS computer</li>
            <li><span>✓</span> Node.js 18+</li>
            <li><span>✓</span> Roblox Studio</li>
            <li><span>✓</span> OpenAI API key</li>
          </ul>
        </div>

        <ol class="steps">
          <li class="step">
            <div class="step-index">01</div>
            <div class="step-content">
              <h3>Download and extract</h3>
              <p>Download the latest main branch, open the ZIP, and move the extracted folder somewhere easy to find.</p>
              <a class="inline-link" href="https://github.com/awantony3/Roblox-Hackathon-Agent/archive/refs/heads/main.zip">Download main.zip ↓</a>
            </div>
          </li>

          <li class="step">
            <div class="step-index">02</div>
            <div class="step-content">
              <h3>Open the project in Terminal</h3>
              <p>Type <code>cd</code>, add a space, drag the extracted project folder into Terminal, and press Return.</p>
              <div class="code-block"><code>cd /path/to/Roblox-Hackathon-Agent-main</code><button class="copy-button" data-copy="cd /path/to/Roblox-Hackathon-Agent-main">Copy</button></div>
            </div>
          </li>

          <li class="step">
            <div class="step-index">03</div>
            <div class="step-content">
              <h3>Create the environment file</h3>
              <p>Copy the safe example configuration, then open the new <code>.env</code> 
[truncated — 7359 more characters]
```

### website/styles.css

```css
:root {
  --ink: #f3f2ec;
  --muted: #9c9d9a;
  --dim: #6d6f6c;
  --bg: #0b0d0c;
  --panel: #121513;
  --panel-2: #171a18;
  --line: #2a2e2b;
  --green: #a7ff54;
  --cyan: #67e8d2;
  --orange: #ff8b4c;
  --mono: "DM Mono", monospace;
  --sans: "Manrope", system-ui, sans-serif;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
body { margin: 0; background: var(--bg); color: var(--ink); font-family: var(--sans); min-height: 100vh; }
body::before { content: ""; position: fixed; inset: 0; pointer-events: none; background: radial-gradient(circle at 78% 12%, rgba(103, 232, 210, .09), transparent 28%), radial-gradient(circle at 8% 42%, rgba(167, 255, 84, .045), transparent 26%); z-index: -1; }
.noise { position: fixed; inset: 0; pointer-events: none; opacity: .18; z-index: 20; background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.92' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.16'/%3E%3C/svg%3E"); }
a { color: inherit; }
code { font-family: var(--mono); }

.site-header { height: 76px; max-width: 1240px; margin: 0 auto; padding: 0 28px; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid var(--line); }
.brand { display: inline-flex; align-items: center; gap: 12px; color: var(--ink); text-decoration: none; font-family: var(--mono); font-size: 13px; letter-spacing: .08em; font-weight: 500; }
.brand-muted { color: var(--muted); }
.brand-mark { width: 28px; height: 28px; border: 2px solid var(--green); transform: rotate(12deg); display: grid; place-items: center; }
.brand-mark span { width: 9px; height: 9px; background: var(--green); }
nav { display: flex; gap: 30px; }
nav a { text-decoration: none; color: var(--muted); font-size: 13px; font-weight: 600; transition: color .2s; }
nav a:hover { color: var(--ink); }
.nav-download { color: #0d120a; background: var(--green); padding: 10px 13px; margin-top: -10px; margin-bottom: -10px; }
.nav-download:hover { color: #0d120a; background: #bbff7a; }

main { overflow: hidden; }
.hero { max-width: 1240px; min-height: 670px; margin: 0 auto; padding: 92px 28px 72px; display: grid; grid-template-columns: 1.08fr .92fr; gap: 70px; align-items: center; border-bottom: 1px solid var(--line); }
.eyebrow, .kicker { font-family: var(--mono); font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--green); }
.eyebrow { display: flex; align-items: center; gap: 9px; margin-bottom: 24px; }
.status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 14px var(--green); }
h1 { margin: 0; font-size: clamp(48px, 6vw, 78px); line-height: .98; letter-spacing: -.055em; max-width: 720px; }
h1 span { color: var(--muted); }
.hero-lede { max-width: 620px; color: #b7b9b5; font-size: 18px; line-height: 1.7; margin: 28px 0 34px; }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.button { min-height: 50px; padding: 0 20px; display: inline-flex; align-items: center; justify-content: center; gap: 26px; border: 1px solid var(--line); text-decoration: none; font-family: var(--mono); font-size: 12px; font-weight: 500; cursor: pointer; transition: transform .2s, background .2s, border-color .2s; }
.button:hover { transform: translateY(-2px); }
.button-primary { color: #0d120a; background: var(--green); border-color: var(--green); }
.button-primary:hover { background: #bbff7a; }
.button-secondary { background: var(--panel); color: var(--ink); }
.button-secondary:hover { border-color: #535953; }
.download-note { margin: 16px 0 0; color: var(--dim); font-family: var(--mono); font-size: 10px; }
.download-main { min-width: 280px; font-weight: 600; }

.start-card { background: linear-gradient(145deg, rgba(25,30,27,.98), rgba(13,16,14,.98)); border: 1px solid #3b433d; box-shadow: 0 40px 90px rgba(0,0,0,.38); }
.start-card-header { min-height: 50px; padding: 0 18px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; color: var(--green); font-family: var(--mono); font-size: 9px; letter-spacing: .1em; }
.start-list { list-style: none; margin: 0; padding: 0 18px; }
.start-list li { min-height: 48px; display: grid; grid-template-columns: 30px 1fr; align-items: center; border-bottom: 1px solid var(--line); }
.start-list li > span { width: 20px; height: 20px; display: grid; place-items: center; border: 1px solid #465047; color: var(--green); font-family: var(--mono); font-size: 8px; }
.start-list div { display: flex; align-items: baseline; justify-content: space-between; gap: 15px; }
.start-list strong { font-size: 12px; }
.start-list small { color: var(--dim); font-family: var(--mono); font-size: 8px; text-align: right; }
.start-card-link { min-height: 50px; display: flex; align-items: center; justify-content: center; color: #11170d; background: var(--green); text-decoration: none; font-family: var(--mono); font-size: 10px; font-weight: 500; }
.start-card-link:hover { background: #bbff7a; }

.agent-card { min-height: 470px; background: linear-gradient(145deg, rgba(25,30,27,.98), rgba(13,16,14,.98)); border: 1px solid #323833; position: relative; box-shadow: 0 40px 90px rgba(0,0,0,.38); }
.agent-card::before { content: ""; position: absolute; inset: 0; background-image: linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px), linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px); background-size: 24px 24px; pointer-events: none; }
.card-topbar { height: 48px; border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; padding: 0 16px; font-family: var(--mono); color: var(--muted); font-size: 10px; }
.card-topbar div { display: flex; gap: 6px; }
.card-topbar i { display: block; width: 6px; height: 6px; border-radius: 50%; background: #3b403d; }
.runtime-visual { 
[truncated — 10214 more characters]
```

### backend/src/doctor.ts

```typescript
import { runReadinessChecks } from "./readiness.js";

async function main() {
  const report = await runReadinessChecks();
  console.log("Roblox Hackathon Agent doctor\n");
  for (const check of report.checks) console.log(`${check.ok ? "PASS" : "FAIL"}  ${check.name}: ${check.detail}`);
  console.log(`\n${report.ready ? "READY" : "NOT READY"}`);
  process.exitCode = report.ready ? 0 : 1;
}

void main();

```

### backend/tests/Readiness.test.ts

```typescript
import test from "node:test";
import assert from "node:assert/strict";
import { runReadinessChecks } from "../src/readiness.js";

test("readiness reports required files and configuration without secrets", async () => {
  const report = await runReadinessChecks("/definitely-not-a-real-home");
  assert.equal(report.checks.some((check) => check.name === "Studio plugin source" && check.ok), true);
  assert.equal(report.checks.some((check) => check.name === "Installed Studio plugin" && !check.ok), true);
  assert.equal(JSON.stringify(report).includes("sk-proj"), false);
});

```

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