# Project export: GridMind

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: UC Berkeley AI Hackathon 2026
- Tagline: An autonomous infrastructure router that treats cloud compute like a high-frequency energy arbitrage market, dynamically shifting workloads to the cheapest, cleanest data centers in O(1) time.
- Devpost: https://devpost.com/software/gridmind-t42omn
- GitHub: https://github.com/saosin06/Gridmind
- Demo: https://gridmind-six.vercel.app/
- Video: https://www.youtube.com/embed/F1bXl50o0kM?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Claude Opus 4.8 (1M context) (19 commits), Nick Costello (19 commits), fly-io[bot] (1 commits)

## Devpost submission (written by the team)

### Inspiration

Energy costs represent 30-40% of AI infrastructure spend at the hyperscale companies. Yet workload routing is static; we route code and models to regions without considering the actual physical state of the grid. Electricity wholesale prices swing 10x intraday, and cooling efficiency is thermodynamically linked to ambient temperature; even the materials used to produce power are not the same across the country. California runs 100% renewels during solar peaks; Texas burns coal at night, meanwhile, nobody is optimising for this. We asked, What if routing decisions were made in real time, physics-aware, and carbon-conscious? What it Does Ingests live grid data from 3 US datacenter regions (San Jose/CAISO, Ashburn/PJM, Austin/ERCOT): wholesale prices, carbon intensity, 24-hour forecasts, and ambient temperature. Scores regions on normalized cost, facility efficiency (PUE), carbon, and latency. Presets for Training (carbon-first), Inference (latency-first), Batch (cost-first). Claude's agent decides which region to route to—now or defer to a cleaner hour—with reasoning backed by actual numbers. The model judges; C++ does the math. Takes real action: Opens a GitHub PR with a Kubernetes manifest pinned to the chosen region, or boots a real Fly.io machine in that region. Verifiable. Auto-cleaned. Fleet autopilot routes entire job queues autonomously, accumulating savings with Claude-written explanations.

### How we built it

Layer 1: Data Ingestion (Node.js/Next.js) Parallel API calls via Promise.allSettled() 3-second timeouts per API Graceful fallbacks to historical data Layer 2: Optimization Engine (C++ → WebAssembly) Priority queue for O(1) optimal region lookup Thermodynamic PUE scaling based on live temperature Compiled via Emscripten: 10x faster than JavaScript Layer 3: Autonomous Agent (Claude Sonnet 4.6) Tool-use pattern with 5 callable functions Claude autonomously chains tools and makes routing decisions Explains reasoning and acknowledges risks Layer 4: Dashboard (React/Tailwind/Recharts) Real-time region matrix, cost calculator, carbon forecast Claude's full analysis with risk warnings *Layer 5: MCP * -fully built MCP allowing calls from agents and AI, fully autonomous workflow

### Challenges we ran into

Finding real price data: Our first source (EIA) returns demand in MWH, not price, so we switched to GridStatus.io real-time LMP/SPP feed. Finding real price data: Our first source (EIA) returns demand in MWH, not price, so we switched to GridStatus.io real-time LMP/SPP feed. Score normalizations: carbon intensity (hundreds) initially swamped price and PUE, so weights and presents didn't change the ranking until we normalized every factor onto a common scale. Score normalizations: carbon intensity (hundreds) initially swamped price and PUE, so weights and presents didn't change the ranking until we normalized every factor onto a common scale. Making the agent fast: a multi-step tool chain, extended thinking took 30-40 seconds. We preloaded all the data into one forced tool call and dropped to 7 seconds while not losing any decision quality. Making the agent fast: a multi-step tool chain, extended thinking took 30-40 seconds. We preloaded all the data into one forced tool call and dropped to 7 seconds while not losing any decision quality. Free tier rate limits: GridStatus 1-req/s + monthly quota forced sequential cached fetching. Free tier rate limits: GridStatus 1-req/s + monthly quota forced sequential cached fetching.

### Accomplishments we're proud of

Built a full loop system: not just a dashboard that recommends, but an agent that decides within guardrails and provisions real compute in the region that the math determined was the most optimal. Built a full loop system: not just a dashboard that recommends, but an agent that decides within guardrails and provisions real compute in the region that the math determined was the most optimal. A real Fly.io machine booting in the agent-selected region. A real Fly.io machine booting in the agent-selected region.

### What we learned

LLMs can't do it all, using an LLM or agent where it would be truly beneficial and not just to say we have it in our tech stack. -Guardrails belong in code, not in a prompt: filter the choice before the model -Context is king: pre-loading data into one call is dramatically faster than letting the agent fetch it step by step

### What's next

f3or Gridmind LLMs can't do it all, using an LLM or agent where it would be truly beneficial and not just to say we have it in our tech stack. -Guardrails belong in code, not in a prompt: filter the choice before the model -Context is king: pre-loading data into one call is dramatically faster than letting the agent fetch it step by step What's next f3or Gridmind Bring your own cloud: each enterprise connects their own system so the agent can route into the customer's infrastructure ( Slurm, AWS/GCP), not a pooled account) Bring your own cloud: each enterprise connects their own system so the agent can route into the customer's infrastructure ( Slurm, AWS/GCP), not a pooled account)

## README (from the GitHub repository)

# GridMind

**Carbon- and cost-aware compute routing.** GridMind watches the live electricity grid and routes compute workloads to the **cheapest, cleanest** region and time — then an AI agent decides and **takes real action**, all within hard policy guardrails.

🔗 **Live demo:** https://gridmind-six.vercel.app

---

## The problem

AI and large-scale compute are exploding in both **cost** and **carbon**. But where and when you run a workload matters enormously:

- **Wholesale electricity prices swing wildly** by region and by hour — sometimes *negative* (surplus solar in California) and sometimes 3–5× higher elsewhere on the same grid.
- **Grid carbon intensity** varies just as much: a job in California at midday can be ~50 gCO₂/kWh while the same job in another region is ~400.

Yet teams typically pick a cloud region **once, statically**, and run everything there — leaving real money and emissions on the table. GridMind closes that gap.

## What it does

```
Live grid data  →  Agent decides (where + when)  →  Guardrails  →  Real action
  (price/carbon/      (Claude, with reasoning        (policy enforced    (GitHub PR
   forecast/latency)   + deterministic scoring)       in code)            or real deploy)
```

1. **Pulls live data** — real-time wholesale prices (GridStatus.io), grid carbon intensity + 24h forecast (Electricity Maps), and weather (OpenWeatherMap) for three real regions: **San Jose (CAISO)**, **Ashburn (PJM)**, **Austin (ERCOT)**.
2. **Scores every region** on a normalized blend of **cost, efficiency (PUE), carbon, and latency** — weights are tunable, with presets for *Training* (carbon-first), *Inference* (latency-first), and *Batch* (cost-first).
3. **An AI routing agent** (Claude) chooses the region **and** whether to run now or **defer** to a cleaner upcoming hour, and explains its reasoning.
4. **Guardrails are enforced in code** — policy constraints (allowed regions, max latency, max carbon) hard-filter the candidates *before* the model sees them, so it physically cannot violate them. All cost/CO₂ numbers are recomputed deterministically: **the model judges, the code does the arithmetic.**
5. **It takes real action:**
   - **GitOps PR** — opens a real GitHub pull request with a Kubernetes manifest pinned to the chosen region.
   - **Real deploy** — boots an actual [Fly.io](https://fly.io) machine in the chosen region (`sjc`/`iad`/`dfw`), then auto-destroys it.
6. **Fleet Autopilot** — a continuous scheduler routes a queue of jobs to the best region/time on its own, accumulating savings, with periodic LLM-written **operator briefings**.

## How the agent works

- **`/api/decide`** runs the routing agent. It pre-loads the live conditions, forecast, and projected costs, then makes a **single forced-tool call** (Claude **Sonnet 4.6**) that returns a structured decision — fast (~7s) and deterministic in shape.
- **Guardrails in code, not the prompt:** the policy filter removes disallowed regions up front; if none qualify, it returns a clean refusal *without* calling the model.
- **Deterministic recompute:** the agent picks the region and timing; the server recomputes the cost, CO₂, and savings, so the displayed numbers are always trustworthy.
- **Right tool for the job:** Claude is used where judgment matters (the decision, the operator briefings). The **fleet scheduler routes deterministically** in its hot loop — no LLM per job — so it scales, with the LLM reserved for the human-facing narrative.

## Architecture

```mermaid
flowchart LR
  GS[GridStatus.io<br/>prices] --> D
  EM[Electricity Maps<br/>carbon + forecast] --> D
  OW[OpenWeatherMap] --> D
  subgraph Core["lib/gridmind (shared core)"]
    D[data.ts<br/>fetchers + cache] --> S[scoring.ts<br/>normalized weights + latency]
    D --> E[economics.ts]
    D --> SC[scheduler.ts]
  end
  subgraph API["pages/api"]
    AGG[aggregate / weather / forecast]
    SCORE[score]
    DECIDE[decide → Claude]
    SCHED[schedule]
    BRIEF[briefing → Claude]
    PR[deploy-pr → GitHub]
    FLY[deploy-fly → Fly.io]
  end
  Core --> API
  UI[Dashboard: Overview · Routing Agent · Fleet Autopilot] --> API
  DECIDE --> PR
  DECIDE --> FLY
```

- **Single source of truth** in `lib/gridmind/` — every route and the agent reuse the same scoring, data, and economics functions.
- **Three-tab UI** (`components/`): **Overview** (live monitoring), **Routing Agent** (single-job decide → real action), **Fleet Autopilot** (autonomous scheduler + briefings).

## Tech stack

- **Next.js 16** (Pages Router) · **React 19** · **TypeScript** · **Tailwind CSS v4**
- **Anthropic SDK** (Claude Sonnet 4.6) for the routing agent and operator briefings
- **GridStatus.io**, **Electricity Maps**, **OpenWeatherMap** for live grid + weather data
- Real actions via the **GitHub** and **Fly.io Machines** REST APIs
- Deployed on **Vercel** (auto-deploys on push; data endpoints edge-cached with stale-while-revalidate)

## Integrations — how systems plug in

GridMind is **API-first**: a dashboard is for evaluating and observing, but real adoption is GridMind embedded in your pipeline.

- **REST API** — `POST /api/decide` (agentic decision) and `POST /api/schedule` (fast deterministic batch routing) return `{region, run_now, defer_hours, projected, savings}`. Call them from your orchestration code:
  ```bash
  curl -s -X POST https://gridmind-six.vercel.app/api/schedule \
    -H 'content-type: application/json' \
    -d '{"jobs":[{"id":"job1","mw":50,"hours":12,"flexible":true,"profile":"training"}]}'
  ```
- **MCP server** (`mcp/`) — exposes GridMind as tools (`get_grid_conditions`, `route_workload`, `deploy_to_region`, `open_deployment_pr`) so any agent — Claude Code, Claude Desktop, your own — can **observe → route → act**. See [`mcp/README.md`](mcp/README.md).
- **GitOps** — the agent opens a real pull request with a Kubernetes manifest pinned to the chosen region; merge to deploy. Drops into existing CI/CD.

> Roadmap: Kubernetes scheduler plugin, Slurm/Airflow/Ray operators, Terraform provider, and a CLI — so workloads are placed with no human in the loop.

## Run locally

```bash
npm install
cp .env.example .env.local   # then fill in your keys
npm run dev                  # http://localhost:3000
```

Required environment variables (see `.env.example`):

| Variable | Used for |
|---|---|
| `ANTHROPIC_API_KEY` | the routing agent + operator briefings |
| `GRIDSTATUS_API_KEY` | real-time wholesale electricity prices |
| `ELECTRICITY_MAPS_AUTH_TOKEN` | grid carbon intensity + 24h forecast |
| `OPENWEATHERMAP_API_KEY` | regional weather |
| `GITHUB_TOKEN` | *(optional)* GitOps PR action — Contents + Pull requests: R/W |
| `FLY_API_TOKEN` | *(optional)* real Fly.io deploy — a **deploy** token |

> Without the optional tokens, the agent still decides and explains; the deploy buttons just won't fire. Without the data keys, prices/carbon fall back to representative constants.

## Roadmap (productionization)

- **Bring-your-own-cloud:** per-tenant credential vaults so the agent deploys into the *customer's* infrastructure (Kubernetes / cloud / Slurm), not a shared account.
- **More regions** — a full multi-ISO fleet view.
- **MCP server** — expose GridMind's capabilities as tools any agent can call.
- **Closed-loop learning** — measure predicted vs. actual and adjust future decisions.
- **Eval pipeline** — automated guardrail / optimality / consistency tests.

---

*Built at a hackathon. The deploys target a demo Fly.io account; in production each company connects its own cloud.*


## Detected evidence (automated analysis)

Indexed codebase: 37 recognized source files, 242 KB.
- Anthropic (technology) — detected in the code
- C++ (language) — detected in the code
- CSS (language) — detected in the code
- JavaScript (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- Vercel (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (49 of 49)

```
.dockerignore
.env.example
.gitignore
AGENTS.md
app/globals.css
app/layout.tsx
CLAUDE.md
components/AgentPanel.tsx
components/CountUp.tsx
components/Dashboard.tsx
components/DevPanel.tsx
components/FleetScheduler.tsx
DEMO.md
docker-entrypoint.js
Dockerfile
eslint.config.mjs
fly.toml
lib/gridmind/data.ts
lib/gridmind/economics.ts
lib/gridmind/manifest.ts
lib/gridmind/regions.ts
lib/gridmind/scheduler.ts
lib/gridmind/scoring.ts
lib/loadScorer.ts
lib/scorer.cpp
mcp/package.json
mcp/README.md
mcp/server.mjs
mcp/test.mjs
next.config.ts
package.json
pages/_app.tsx
pages/api/aggregate.ts
pages/api/analyze.ts
pages/api/briefing.ts
pages/api/carbon.ts
pages/api/decide.ts
pages/api/deploy-fly.ts
pages/api/deploy-pr.ts
pages/api/forecast.ts
pages/api/pricing.ts
pages/api/schedule.ts
pages/api/score.ts
pages/api/weather.ts
pages/index.tsx
postcss.config.mjs
public/scorer.js
README.md
tsconfig.json
```

### Dependencies

- mcp/package.json: @modelcontextprotocol/sdk@^1.13.0, zod@^3.23.8
- package.json: @anthropic-ai/sdk@^0.105.0, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, eslint@^9, eslint-config-next@16.2.9, next@16.2.9, react@19.2.4, react-dom@19.2.4, react-markdown@^10.1.0, recharts@^3.8.1, remark-gfm@^4.0.1, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- Add Developers button + integration panel (REST API + MCP setup) to the header
- MCP demo test: route the same workload under multiple profiles/locations
- Add GridMind MCP server: observe -> route -> act as tools for any agent
- Fix agent always picking San Jose: anchor region selection on composite_score
- Add live power-generation mix per region (renewable %, fossil-free %, top source)
- Demo polish: favicon + OG meta, animated count-up on fleet savings, savings headline on Overview
- Add dynamic cooling model: live ambient temperature -> effective PUE
- Add README, demo script, and .env.example
- Speed up /api/decide ~5x: single forced-tool call with pre-loaded data, no tool loop/thinking (33-44s -> ~7s)
- Switch /api/decide agent to claude-sonnet-4-6 for faster decisions
- Raise /api/aggregate maxDuration to 30s so live GridStatus pricing completes (was timing out -> fallback constants)
- Edge-cache data endpoints (s-maxage + stale-while-revalidate) to fix slow cold loads
- Fly deploy: longer-lived machine (10m) + self-proving inline result (id+region); label link as owner console
- Fix Vercel 500: rename /api/route -> /api/score ('route' collides with App Router reserved filename)
- Real agent actions (GitOps PR + Fly deploy), fleet briefings, UI reformat
- Merge pull request #3 from saosin06/flyio-new-files
- New files from Fly.io Launch
- Add routing agent: /api/decide + AgentPanel, refactor core into lib/gridmind
- Add latency scoring, presets, cost calculator, and carbon time-shift
- Redesign dashboard + fix WASM, telemetry, Claude report

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

### CLAUDE.md

```markdown
@AGENTS.md

```

### AGENTS.md

```markdown
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->

```

### package.json

```
{
  "name": "gridmind",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint"
  },
  "dependencies": {
    "@anthropic-ai/sdk": "^0.105.0",
    "next": "16.2.9",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "react-markdown": "^10.1.0",
    "recharts": "^3.8.1",
    "remark-gfm": "^4.0.1"
  },
  "devDependencies": {
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "16.2.9",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### Dockerfile

```
# syntax = docker/dockerfile:1

# Adjust NODE_VERSION as desired
ARG NODE_VERSION=22.21.1
FROM node:${NODE_VERSION}-slim AS base

LABEL fly_launch_runtime="Next.js"

# Next.js app lives here
WORKDIR /app

# Set production environment
ENV NODE_ENV="production"


# Throw-away build stage to reduce size of final image
FROM base AS build

# Install packages needed to build node modules
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential node-gyp pkg-config python-is-python3

# Install node modules
COPY package-lock.json package.json ./
RUN npm ci --include=dev

# Copy application code
COPY . .

# Build application
RUN npx next build --experimental-build-mode compile

# Remove development dependencies
RUN npm prune --omit=dev


# Final stage for app image
FROM base

# Copy built application
COPY --from=build /app /app

# Entrypoint sets up the container.
ENTRYPOINT [ "/app/docker-entrypoint.js" ]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD [ "npm", "run", "start" ]

```

### mcp/package.json

```
{
  "name": "gridmind-mcp",
  "version": "1.0.0",
  "private": true,
  "type": "module",
  "description": "MCP server exposing GridMind carbon/cost-aware compute routing as tools",
  "bin": { "gridmind-mcp": "./server.mjs" },
  "scripts": { "start": "node server.mjs", "test": "node test.mjs" },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.13.0",
    "zod": "^3.23.8"
  }
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      lang="en"
      className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col">{children}</body>
    </html>
  );
}

```

### pages/index.tsx

```typescript
import { Component, ReactNode } from 'react'
import Head from 'next/head'
import Dashboard from '../components/Dashboard'

// Error boundary (class component — required for componentDidCatch)
class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
  state = { error: null as Error | null }

  static getDerivedStateFromError(error: Error) {
    return { error }
  }

  componentDidCatch(error: Error) {
    console.error('[Dashboard ErrorBoundary]', error)
  }

  render() {
    if (this.state.error) {
      return (
        <div className="grid min-h-screen place-items-center bg-[#070b12] p-6 text-slate-100">
          <div className="max-w-md text-center">
            <h1 className="mb-2 text-xl font-semibold text-rose-400">Dashboard crashed</h1>
            <p className="mb-4 text-sm text-slate-400">{this.state.error.message}</p>
            <button
              onClick={() => this.setState({ error: null })}
              className="rounded-md bg-slate-800 px-4 py-2 text-sm transition hover:bg-slate-700"
            >
              Retry
            </button>
          </div>
        </div>
      )
    }
    return this.props.children
  }
}

export default function Home() {
  return (
    <>
      <Head>
        <title>GridMind — Carbon & Cost-Aware Compute Routing</title>
        <meta name="description" content="GridMind routes compute workloads to the cheapest, cleanest region and time using live grid data — and an AI agent that decides and takes real action, within hard guardrails." />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
        <meta property="og:type" content="website" />
        <meta property="og:title" content="GridMind — Carbon & Cost-Aware Compute Routing" />
        <meta property="og:description" content="Live grid data → an AI agent that routes compute to the cheapest, cleanest region and time, within guardrails — and takes real action." />
        <meta property="og:url" content="https://gridmind-six.vercel.app" />
        <meta name="twitter:card" content="summary" />
        <meta name="twitter:title" content="GridMind — Carbon & Cost-Aware Compute Routing" />
        <meta name="twitter:description" content="An AI agent that routes compute to the cheapest, cleanest region and time — and takes real action." />
      </Head>
      <ErrorBoundary>
        <Dashboard />
      </ErrorBoundary>
    </>
  )
}

```

### mcp/server.mjs

```
#!/usr/bin/env node
// GridMind MCP server — exposes carbon/cost-aware compute routing as tools any
// MCP client (Claude Code, Claude Desktop, etc.) can call. It's a thin client
// over the live GridMind API, so no API keys are needed locally.
//
// Config (GRIDMIND_URL env, defaults to the deployed instance).

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import { z } from 'zod'

const BASE = process.env.GRIDMIND_URL || 'https://gridmind-six.vercel.app'
const PROFILE = z.enum(['training', 'inference', 'batch', 'balanced'])
const REGION = z.enum(['San Jose', 'Ashburn', 'Austin'])
const USER_LOC = z.enum(['us-east', 'us-west', 'us-central', 'eu', 'apac'])

async function api(path, init) {
  const res = await fetch(`${BASE}${path}`, init)
  const json = await res.json().catch(() => ({}))
  if (!res.ok) throw new Error(json?.error || `${path} -> HTTP ${res.status}`)
  return json
}
const ok = (obj) => ({ content: [{ type: 'text', text: JSON.stringify(obj, null, 2) }] })
const fail = (msg) => ({ content: [{ type: 'text', text: `Error: ${msg}` }], isError: true })

const server = new McpServer({ name: 'gridmind', version: '1.0.0' })

// ── Observe ───────────────────────────────────────────────────────────
server.registerTool(
  'get_grid_conditions',
  {
    title: 'Get live grid conditions',
    description:
      'Live conditions for each data-center region (San Jose/CAISO, Ashburn/PJM, Austin/ERCOT): wholesale electricity price ($/MWh), grid carbon intensity (gCO2/kWh), renewable share (%), dominant generation source, temperature-adjusted PUE, and ambient temperature. Call this first to see the current state of the grid.',
  },
  async () => {
    try { return ok(await api('/api/aggregate')) } catch (e) { return fail(e.message) }
  }
)

server.registerTool(
  'get_carbon_forecast',
  {
    title: 'Get 24h carbon forecast',
    description:
      'Next-24-hour grid carbon-intensity forecast per region, with the cleanest upcoming hour. Use this to decide whether a flexible workload should be deferred to a lower-carbon window.',
  },
  async () => {
    try {
      const fc = await api('/api/forecast')
      const summary = (fc || []).map((r) => {
        const pts = r.forecast || []
        const cleanest = pts.length ? pts.reduce((a, b) => (b.carbon < a.carbon ? b : a)) : null
        return {
          region: r.region,
          current_carbon: pts[0]?.carbon ?? null,
          cleanest_carbon: cleanest?.carbon ?? null,
          points: pts.length,
        }
      })
      return ok(summary)
    } catch (e) { return fail(e.message) }
  }
)

// ── Decide ────────────────────────────────────────────────────────────
server.registerTool(
  'route_workload',
  {
    title: 'Route a compute workload',
    description:
      'Recommend the optimal region AND timing (run now or defer) for a single workload, given a priority profile and where the users are. Returns the chosen region, whether to defer (and by how many hours), the projected energy cost and CO2, and the savings versus the worst region. Deterministic and fast.',
    inputSchema: {
      mw: z.number().positive().describe('Power draw in megawatts'),
      hours: z.number().positive().describe('Duration in hours'),
      profile: PROFILE.default('balanced').describe('training = carbon-first, inference = latency-first, batch = cost-first, balanced = even'),
      flexible: z.boolean().default(true).describe('Whether the job may be deferred to a cleaner window'),
      user_location: USER_LOC.default('us-east').describe('Where the workload\'s users are (affects latency)'),
      max_carbon: z.number().optional().describe('Optional policy cap on grid carbon intensity (gCO2/kWh)'),
    },
  },
  async ({ mw, hours, profile, flexible, user_location, max_carbon }) => {
    try {
      const policy = max_carbon != null ? { max_carbon } : {}
      const { plans } = await api('/api/schedule', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ jobs: [{ id: 'workload', mw, hours, flexible, profile }], policy, user_location }),
      })
      return ok(plans?.[0] ?? { error: 'no plan returned' })
    } catch (e) { return fail(e.message) }
  }
)

// ── Act (real side effects) ───────────────────────────────────────────
server.registerTool(
  'deploy_to_region',
  {
    title: 'Deploy a workload to a region (real)',
    description:
      'REAL SIDE EFFECT: provisions an actual short-lived compute machine on Fly.io in the given region to run the workload (it auto-destroys after a few minutes). Use after route_workload to act on the decision.',
    inputSchema: {
      region: REGION.describe('Region to deploy to'),
      workload_name: z.string().describe('Name/label for the workload'),
    },
  },
  async ({ region, workload_name }) => {
    try {
      return ok(await api('/api/deploy-fly', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ region, workload: { name: workload_name } }),
      }))
    } catch (e) { return fail(e.message) }
  }
)

server.registerTool(
  'open_deployment_pr',
  {
    title: 'Open a deployment pull request (real)',
    description:
      'REAL SIDE EFFECT: opens an actual GitHub pull request containing a Kubernetes manifest that pins the workload to the given region (GitOps). Computes the projected cost/CO2 for the PR by routing the workload first.',
    inputSchema: {
      region: REGION,
      workload_name: z.string(),
      mw: z.number().positive(),
      hours: z.number().positive(),
      profile: PROFILE.default('balanced'),
    },
  },
  async ({ region, workload_name, mw, hours, profile }) => {
    try {
      const { plans } = await api('/api/schedule', {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ jobs: [{ id: 'w
[truncated — 991 more characters]
```

### next.config.ts

```typescript
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;

```

### docker-entrypoint.js

```javascript
#!/usr/bin/env node

const { spawn } = require('node:child_process')

const env = { ...process.env }

;(async() => {
  // If running the web server then prerender pages
  if (process.argv.slice(-3).join(' ') === 'npm run start') {
    await exec('npx next build --experimental-build-mode generate')
  }

  // launch application
  await exec(process.argv.slice(2).join(' '))
})()

function exec(command) {
  const child = spawn(command, { shell: true, stdio: 'inherit', env })
  return new Promise((resolve, reject) => {
    child.on('exit', code => {
      if (code === 0) {
        resolve()
      } else {
        reject(new Error(`${command} failed rc=${code}`))
      }
    })
  })
}

```

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