# Project export: MacroWeaver

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: A simulation engine where AI agents weave markets, economies, and collective behavior from the bottom up.
- Devpost: https://devpost.com/software/macroweaver
- GitHub: https://github.com/siruizou2005/MacroWeaver
- Demo: https://macroweaver.siruizou.com/
- Team: 1 GitHub contributor(s) — siruizou2005 (18 commits)

## Devpost submission (written by the team)

### Overview

A simulation platform for studying how collective behaviors emerge from AI agents.

### Inspiration

Recent research has shown that LLM agents can be used as a new tool for social science research. Studies such as Fish et al. (2024) and Horton's Homo Silicus suggest that AI agents may help researchers explore economic and social behaviors at a scale that was previously impossible. Today, most AI-based social science tools follow a survey paradigm: agents are treated as respondents and researchers collect answers through prompts and questionnaires. We became interested in a different question: What happens when agents interact with each other through markets, institutions, and environments instead of simply answering questions? This inspired us to build MacroWeaver.

### What it does

MacroWeaver is a simulation platform for studying how collective behaviors emerge from AI agents. Rather than treating agents as survey respondents or chatbots, MacroWeaver places them inside shared environments where they make decisions, interact through objective rules, and learn from feedback over time. The engine follows a simple loop: By changing only the underlying mechanism, the same engine can simulate markets, economies, and financial systems. This allows researchers to explore how phenomena such as collusion, inflation, and market dynamics can emerge from the interactions of many individual agents. Our goal is to build a reusable research infrastructure for the next generation of AI-powered social science.

### How we built it

To solve this, we separated the simulation into reusable components: Agent profiles Private state Memory and reflection Decision policies Market mechanisms Scheduling Recording This allowed us to treat the market mechanism as a pluggable module while keeping the rest of the simulation pipeline unchanged. The result is a framework where very different research environments can be built on top of the same core engine rather than requiring entirely separate systems.

### Challenges we ran into

The biggest challenge was finding a common abstraction across very different socio-economic simulations. At first glance, Fish, EconAgent, and financial market simulations appear to be completely different systems. They involve different agents, environments, market structures, and evaluation metrics. However, after studying these papers, we realized they share a common underlying pattern: The challenge was designing a framework flexible enough to support all of these settings without creating a custom implementation for every paper.

### Accomplishments we're proud of

Successfully built a unified simulation framework capable of supporting multiple socio-economic environments. Demonstrated that the same agent architecture can be reused across very different research settings simply by swapping the underlying mechanism. Rather than building a single simulation, we built infrastructure that future simulations can be built upon.

### What we learned

Building MacroWeaver taught us that the environment is often more important than the agent itself. Interesting social and economic phenomena do not come from individual intelligence alone — they emerge from repeated interactions, incentives, and feedback loops. Mechanism design provides a powerful way to study collective AI behavior beyond simple prompt engineering.

### What's next

We plan to expand MacroWeaver into an open research platform for AI-assisted social science: Add more simulation environments Build a shared library of reusable mechanisms Support larger populations of agents Improve tools for visualization and analysis Our long-term goal is to provide a common infrastructure for studying how collective behaviors emerge in AI societies.

## README (from the GitHub repository)

# MacroWeaver

**A generative socio-economic simulation engine with a web console.** One fixed
five-primitive kernel — *Population·Agents → Mechanism(Market) → Observation → Scheduler →
Recorder → write-back* — where **the Market is the only swappable block**. Swap it and the
*same* agents reproduce a different paper:

| Preset | Market | Result |
|---|---|---|
| **Fish · Calvano** (primary) | logit posted-price pricing | algorithmic collusion — price drifts from **Bertrand‑Nash ≈1.47** toward **monopoly ≈1.92** with no communication |
| **EconAgent · Macro** | labor + goods clearing | CPI / inflation / unemployment (Phillips) |
| **TwinMarket · CLOB** | limit order book | traded price vs. fair value with stylized facts (volatility clustering) |

Built from the component×paper generalization of four reference projects
(`collusion-Fish2024`, `EconAgent`, `TwinMarket`, and a prediction-market engine). The web
console implements the `MacroWeaver.dc.html` design (Landing → Presets → Console → Replay).

## Architecture (React + Node + Python)

```
React console (web/)  ──run──▶  Node BFF (server/)  ──spawn──▶  Python engine (engine/)
   Vite + Zustand          REST /api + WS /ws            five-primitive kernel
   4-view SPA        ◀── round-by-round NDJSON ───┘      swappable Market plugins
        ▲                                                          │ writes
        └────────── trace.json (replay scrub) ◀── /api/traces ◀────┘
```

- **Python engine** — the kernel `Runner` (deterministic `np.random.SeedSequence`
  substreams, event-sourced canonical JSON), a `Market` ABC with three plugins
  (`fish_calvano`, `econagent`, `clob`), the agent pipeline
  (Profile → Perception → Memory+Reflection → Decision), and two policies behind one
  interface: `DeterministicPolicy` (golden trace, no key) and `ClaudePolicy` (live LLM via
  Anthropic tool-use). Streams NDJSON events per round and writes a self-contained
  `trace.json`.
- **Node BFF** — Express REST (`/api/presets|configs|traces|schema`) + a WebSocket that
  spawns `python -m macroweaver stream`, relays each round event to the browser, and indexes
  the finished trace. Python is the sole writer of traces.
- **React console** — a 4-view SPA (Zustand store mirroring the design's state model):
  Landing, Presets, **Console** (concentric World view · Roster · Engine loop · cohort
  pipeline drawer · Metrics chart · Inspector with the Fish⇄EconAgent⇄CLOB market switch),
  and **Replay** (price-vs-benchmark chart + transport + per-agent reasoning cards).

### Component × paper → module map
| Component | Fish | EconAgent | CLOB | Module |
|---|---|---|---|---|
| heterogeneous profile / private state | cost/quality · price hist | demographics · wealth | biases · holdings | `agent/profile`, `CohortConfig` |
| memory / reflection | notepad+insights | L-round pool · quarterly | BDI · BDI update | `agent/memory`, `agent/reflection` |
| decision / action | set price | work/consume [0,1] | place/hold order | `policy/*`, `market.parse_decision` |
| **market mechanism** | logit demand | labor+goods clearing | order-book matching | `market/{fish_calvano,econagent,clob}` |
| institution / production | – | tax+redistribution · rate · production fn | – | `econagent` params + `LayerConfig` |
| info/news · shock | rival prices | macro indicators · COVID-style | news+sentiment | `market.news_text`, `market.apply_shock` |
| scheduler · recorder · metrics | rounds · collusion index | quarters · inflation/Phillips | sessions · stylized facts | `kernel/{scheduler,recorder}`, `metrics/*` |

## Quick start

```bash
# 1. engine (Python) — golden traces need no API key
cd engine && python3 -m venv .venv && ./.venv/bin/pip install -e '.[dev,llm]'
cd ..

# 2. server + web (Node workspaces)
npm install

# 3. run all three tiers (server :8787, vite :5173 with /api+/ws proxy)
npm run dev          # then open http://127.0.0.1:5173
```

In the console: pick **Fish · Calvano**, press **▶ Run**, watch the collusion curve form
live on the canvas, then scrub it in **Replay** with each agent's reasoning per round. Use
the Inspector's **market switch** to swap to EconAgent or CLOB, or flip a cohort to
**Claude (live)** (needs `ANTHROPIC_API_KEY`).

### Engine CLI (no Node, no key)
```bash
make golden     # deterministic Fish golden trace → traces/golden/fish_calvano.trace.json
make verify     # assert byte-exact determinism
make test       # pytest (golden reproducibility + market contracts)
make schema     # export shared/config.schema.json
# any preset:
engine/.venv/bin/python -m macroweaver golden --config presets/econagent_macro.yaml \
    --out traces/golden/econagent_macro.trace.json
```

## Claude live mode
Set `ANTHROPIC_API_KEY` in `engine/.env` (see `.env.example`). Cohorts with
`policy: claude` then call Claude (`claude-opus-4-8` by default) via forced tool-use for
schema-valid decisions, with an on-disk response cache, retry/backoff and refusal handling.
**Without a key, `claude` cohorts fall back to the deterministic heuristic**, so every demo
still runs and reproduces the curve. The deterministic golden trace is the reproducible,
zero-cost path used for the "golden trace" the design demos.

## Layout
```
engine/macroweaver/  kernel/ (runner,config,events,sinks,scheduler,recorder,replay)
                     market/ (base ABC + fish_calvano, econagent, clob)
                     agent/ (pipeline, memory, reflection)  policy/ (deterministic, claude)
                     metrics/  cli/
server/src/          index, routes, runManager (spawn+relay), files, config
web/src/             App, store; views/; console/{canvas,rail}; replay/; lib/chart
presets/             fish_calvano · econagent_macro · clob_twinmarket  (+ golden traces)
shared/              config.schema.json (generated from pydantic)
```

## Notes
- **Determinism** is the core invariant: a deterministic run is byte-exact reproducible
  (`events.py` canonical JSON, ts masked) — `make verify` and the test suite gate it.
- The CLOB is a compact self-contained equity book (price-time priority, persistent book),
  not the binary-outcome prediction-market engine, so the action space is plain equity
  buy/sell/hold — a better fit for "financial market with stylized facts".
- EconAgent's macro calibration and CLOB's stylized facts are demo-grade; the mechanisms
  are real and pluggable. Optional layers (institution/social/news/shock) are wired as
  config-driven hooks; fiscal/monetary/production and shock injection are active, social
  propagation is a lightweight stub.


## Detected evidence (automated analysis)

Indexed codebase: 82 recognized source files, 425 KB.
- Anthropic (technology) — detected in the code
- CSS (language) — detected in the code
- Express (technology) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Python (language) — detected in the code
- React (technology) — detected in the code
- TypeScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: commit authorship or trailers

## Codebase structure (from repository index)

### Files (94 of 94)

```
.gitignore
engine/.env.example
engine/macroweaver/__init__.py
engine/macroweaver/__main__.py
engine/macroweaver/agent/__init__.py
engine/macroweaver/agent/memory.py
engine/macroweaver/agent/pipeline.py
engine/macroweaver/agent/reflection.py
engine/macroweaver/cli/__init__.py
engine/macroweaver/cli/__main__.py
engine/macroweaver/clob_engine/__init__.py
engine/macroweaver/kernel/__init__.py
engine/macroweaver/kernel/config.py
engine/macroweaver/kernel/events.py
engine/macroweaver/kernel/recorder.py
engine/macroweaver/kernel/replay.py
engine/macroweaver/kernel/runner.py
engine/macroweaver/kernel/scheduler.py
engine/macroweaver/kernel/sinks.py
engine/macroweaver/layers/__init__.py
engine/macroweaver/market/__init__.py
engine/macroweaver/market/base.py
engine/macroweaver/market/econagent_profiles.json
engine/macroweaver/market/econagent.py
engine/macroweaver/market/fish_calvano.py
engine/macroweaver/market/loader.py
engine/macroweaver/metrics/__init__.py
engine/macroweaver/metrics/base.py
engine/macroweaver/metrics/econ.py
engine/macroweaver/metrics/fish.py
engine/macroweaver/policy/__init__.py
engine/macroweaver/policy/base.py
engine/macroweaver/policy/claude_policy.py
engine/macroweaver/policy/factory.py
engine/macroweaver/policy/replay.py
engine/pyproject.toml
engine/tests/__init__.py
engine/tests/test_agents_explicit.py
engine/tests/test_golden_econagent.py
engine/tests/test_golden_fish.py
engine/tests/test_market_contract.py
engine/tests/test_markets.py
Makefile
package.json
presets/econagent_macro.yaml
presets/fish_calvano_monopoly.yaml
presets/fish_calvano.yaml
README.md
server/package.json
server/src/config.js
server/src/files.js
server/src/index.js
server/src/routes.js
server/src/runManager.js
shared/config.schema.json
traces/golden/econagent_macro.trace.json
traces/golden/fish_calvano.trace.json
web/index.html
web/package.json
web/src/App.tsx
web/src/console/canvas/CohortDrawer.tsx
web/src/console/canvas/EngineLoop.tsx
web/src/console/canvas/Roster.tsx
web/src/console/canvas/WorldArena.tsx
web/src/console/library/MarketsPanel.tsx
web/src/console/library/PresetsPanel.tsx
web/src/console/library/SchemaPanel.tsx
web/src/console/library/SettingsPanel.tsx
web/src/console/library/TracesPanel.tsx
web/src/console/marketFields.ts
web/src/console/PresetPicker.tsx
web/src/console/rail/Inspector.tsx
web/src/console/rail/MetricsPanel.tsx
web/src/console/SetupSidebar.tsx
web/src/lib/chart.ts
web/src/lib/defaults.ts
web/src/main.tsx
web/src/replay/AgentQA.tsx
web/src/replay/FlowStrip.tsx
web/src/replay/NewRun.tsx
web/src/replay/PriceChart.tsx
web/src/replay/RunBar.tsx
web/src/replay/ThinkingCards.tsx
web/src/replay/Transport.tsx
web/src/store.ts
web/src/theme.css
web/src/types.ts
web/src/views/Blog.tsx
web/src/views/Console.tsx
web/src/views/Docs.tsx
web/src/views/Landing.tsx
web/src/views/Replay.tsx
web/tsconfig.json
web/vite.config.ts
```

### Dependencies

- engine/pyproject.toml: anthropic@>=0.40, numpy@>=1.26, pydantic@>=2.6, pytest@>=8.0, python-dotenv@>=1.0, pyyaml@>=6.0, sortedcontainers@>=2.4, typer@>=0.12
- package.json: concurrently@^9.1.0
- server/package.json: express@^4.19.2, ws@^8.18.0, yaml@^2.5.0
- web/package.json: @types/react@^18.3.12, @types/react-dom@^18.3.1, @vitejs/plugin-react@^4.3.4, react@^18.3.1, react-dom@^18.3.1, typescript@^5.6.3, vite@^5.4.10, zustand@^5.0.2

### Recent commits (newest first)

- Keep current state visible when a live run is stopped
- Console: order the EconAgent preset first in the picker
- Console: use the woven vector logo mark in the header
- Collapse to fish + econagent markets; revise landing, question panel, replay
- Fix chart/recorder regressions from the bug-fix pass
- Fix bugs across web console, replay, and Node BFF
- Console: Record/write-back node, live per-round record, agent system-prompt display
- Remove non-functional Institution and Social network layers
- Demand Function: collapse to single row, expand on click
- Rename Fish·Calvano → Oligopoly Pricing; URL routing for all console/replay states
- Library: prominent delete/un-publish buttons with two-step confirm
- Library: delete saved configs and un-publish Markets templates (independent)
- Fish preset: faithful Calvano/Fish 2024 algorithmic-collusion (Haiku, P1 duopoly)
- Header back is origin-aware; unify "Library" → "Console"
- Header: contextual "Back" button in the top-left nav
- Console: Save pops a publish-to-Markets panel
- Console: rename/save/publish worlds + Cohorts→Agents with delete
- Console library pages, navigation fixes, and blog page
- Console: make "Start from scratch" a true from-0 (empty roster)
- Console: real from-scratch configurator driven by an engine field registry

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

### package.json

```
{
  "name": "macroweaver",
  "version": "0.1.0",
  "private": true,
  "workspaces": [
    "server",
    "web"
  ],
  "scripts": {
    "dev": "concurrently -n server,web -c green,cyan \"npm:dev:server\" \"npm:dev:web\"",
    "dev:server": "npm --workspace server run dev",
    "dev:web": "npm --workspace web run dev",
    "build": "npm --workspace web run build",
    "start": "npm --workspace server run start"
  },
  "devDependencies": {
    "concurrently": "^9.1.0"
  }
}

```

### server/package.json

```
{
  "name": "macroweaver-server",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js"
  },
  "dependencies": {
    "express": "^4.19.2",
    "ws": "^8.18.0",
    "yaml": "^2.5.0"
  }
}

```

### web/package.json

```
{
  "name": "macroweaver-web",
  "version": "0.1.0",
  "private": true,
  "type": "module",
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  },
  "dependencies": {
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "zustand": "^5.0.2"
  },
  "devDependencies": {
    "@types/react": "^18.3.12",
    "@types/react-dom": "^18.3.1",
    "@vitejs/plugin-react": "^4.3.4",
    "typescript": "^5.6.3",
    "vite": "^5.4.10"
  }
}

```

### engine/pyproject.toml

```
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "macroweaver-engine"
version = "0.1.0"
description = "MacroWeaver — generative socio-economic simulation engine (five-primitive kernel, swappable market)"
requires-python = ">=3.10"
dependencies = [
    "pydantic>=2.6",
    "numpy>=1.26",
    "pyyaml>=6.0",
    "typer>=0.12",
    "sortedcontainers>=2.4",
]

[project.optional-dependencies]
llm = ["anthropic>=0.40", "python-dotenv>=1.0"]
dev = ["pytest>=8.0"]

[project.scripts]
macroweaver = "macroweaver.cli.__main__:app"

[tool.setuptools]
packages = { find = { include = ["macroweaver*"] } }

```

### web/src/main.tsx

```typescript
import React from "react";
import ReactDOM from "react-dom/client";
import "./theme.css";
import { App } from "./App";

ReactDOM.createRoot(document.getElementById("root")!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
);

```

### server/src/index.js

```javascript
import http from "node:http";
import fs from "node:fs";
import path from "node:path";
import express from "express";
import { WebSocketServer } from "ws";
import { PORT, ROOT } from "./config.js";
import { router } from "./routes.js";
import { attachWebSocket } from "./runManager.js";

const app = express();
app.use(express.json({ limit: "4mb" }));

// permissive CORS for local dev (vite proxy normally avoids this, but harmless)
app.use((_req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "Content-Type");
  res.header("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
  next();
});

app.use("/api", router);

// serve the built web bundle in production if it exists
const WEB_DIST = path.join(ROOT, "web", "dist");
if (fs.existsSync(WEB_DIST)) {
  app.use(express.static(WEB_DIST));
  app.get("*", (_req, res) => res.sendFile(path.join(WEB_DIST, "index.html")));
}

// Last-resort guards: a stray async error (e.g. a child-process pipe error) should be
// logged, not allowed to take the whole BFF down and drop every connected client.
process.on("uncaughtException", (err) => {
  console.error("[macroweaver] uncaughtException:", err);
});
process.on("unhandledRejection", (err) => {
  console.error("[macroweaver] unhandledRejection:", err);
});

const server = http.createServer(app);
const wss = new WebSocketServer({ server, path: "/ws" });
attachWebSocket(wss);

server.listen(PORT, () => {
  console.log(`[macroweaver] BFF listening on http://127.0.0.1:${PORT}  (REST /api, WS /ws)`);
});

```

### web/src/App.tsx

```typescript
import { useEffect } from "react";
import { useStore } from "./store";
import type { LibTab, Screen } from "./types";
import { Landing } from "./views/Landing";
import { Docs } from "./views/Docs";
import { Blog } from "./views/Blog";
import { Console } from "./views/Console";
import { Replay } from "./views/Replay";

// Logo mark: two point-symmetric line fans inside a circle, à la a woven yin-yang.
// Generated rather than hand-plotted so the curve stays adjustable via a few params.
const LOGO_R = 37;
function logoFan(rimStart: number, rimSweep: number, neckAngle: number, neckR: number, ease: number, n: number, rotate: number) {
  const rad = (d: number) => (d * Math.PI) / 180;
  const lines: { x1: number; y1: number; x2: number; y2: number }[] = [];
  for (let i = 0; i < n; i++) {
    const t = i / (n - 1);
    const rimA = rad(rimStart + rimSweep * t + rotate);
    const na = rad(neckAngle + rotate);
    const r = neckR * Math.pow(1 - t, ease);
    lines.push({
      x1: 50 + r * Math.cos(na),
      y1: 50 + r * Math.sin(na),
      x2: 50 + LOGO_R * Math.cos(rimA),
      y2: 50 + LOGO_R * Math.sin(rimA),
    });
  }
  return lines;
}
const LOGO_LINES = [...logoFan(-80, 150, -30, 29, 1.4, 15, 0), ...logoFan(-80, 150, -30, 29, 1.4, 15, 180)];

function Logo({ size = 30 }: { size?: number }) {
  return (
    <svg width={size} height={size} viewBox="0 0 100 100" style={{ flex: "none" }}>
      <circle cx={50} cy={50} r={LOGO_R + 1.6} fill="none" stroke="var(--green-d)" strokeWidth={3.2} />
      {LOGO_LINES.map((l, i) => (
        <line key={i} x1={l.x1} y1={l.y1} x2={l.x2} y2={l.y2} stroke="var(--green-d)" strokeWidth={2.2} strokeLinecap="round" />
      ))}
    </svg>
  );
}

function NavLink({ label, target }: { label: string; target: Screen }) {
  const screen = useStore((s) => s.screen);
  const nav = useStore((s) => s.nav);
  const on = screen === target;
  return (
    <a
      onClick={() => nav(target)}
      style={{
        fontSize: 14.5,
        fontWeight: 500,
        padding: "7px 13px",
        borderRadius: 8,
        cursor: "pointer",
        color: on ? "var(--green-d)" : "var(--muted)",
        background: on ? "var(--green-l)" : "transparent",
      }}
    >
      {label}
    </a>
  );
}

function Header() {
  const nav = useStore((s) => s.nav);
  const screen = useStore((s) => s.screen);
  const preset = useStore((s) => s.preset);
  const connected = useStore((s) => s.connected);
  const backToPicker = useStore((s) => s.backToPicker);
  const enterConsole = useStore((s) => s.enterConsole);
  const libTab = useStore((s) => s.libTab);
  const inApp = screen === "console" || screen === "replay";

  // Contextual single step back:
  //  - console home (picker) → Home (landing)
  //  - editor / trace replay → back to the console tab it was entered from
  //  - a replay reached by Running a world → back to that editor
  const TAB_LABEL: Record<LibTab, string> = { presets: "Presets", traces: "Traces", markets: "Markets", schema: "config schema", settings: "Settings" };
  const inEditor = screen === "console" && !!preset;
  const onPicker = screen === "console" && !preset;
  let backLabel: string;
  let goBack: () => void;
  if (onPicker) {
    backLabel = "Home";
    goBack = () => nav("landing");
  } else if (inEditor) {
    backLabel = TAB_LABEL[libTab] || "Console";
    goBack = () => backToPicker();
  } else {
    backLabel = preset ? "Console" : TAB_LABEL[libTab] || "Console";
    goBack = () => nav("console");
  }
  return (
    <header
      style={{
        position: "sticky",
        top: 0,
        zIndex: 40,
        background: "rgba(251,251,250,.86)",
        backdropFilter: "blur(10px)",
        borderBottom: "1px solid var(--border)",
      }}
    >
      <div
        style={{
          maxWidth: 1320,
          margin: "0 auto",
          padding: "0 32px",
          height: 68,
          display: "flex",
          alignItems: "center",
          gap: 36,
        }}
      >
        <div
          onClick={() => nav("landing")}
          style={{ display: "flex", alignItems: "center", gap: 11, cursor: "pointer", flex: "none" }}
        >
          <Logo />
          <span
            style={{
              fontFamily: "'Spectral',serif",
              fontWeight: 600,
              fontSize: 20,
              letterSpacing: "-.2px",
            }}
          >
            MacroWeaver
          </span>
        </div>
        <nav style={{ display: "flex", alignItems: "center", gap: 6 }}>
          {inApp ? (
            // one contextual step back (replay → console, editor → library, …)
            <a
              onClick={goBack}
              style={{ display: "flex", alignItems: "center", gap: 6, fontSize: 14.5, fontWeight: 500, padding: "7px 13px 7px 10px", borderRadius: 8, cursor: "pointer", color: "var(--green-d)", background: "var(--green-l)" }}
            >
              <span style={{ fontSize: 15, lineHeight: 1 }}>←</span> Back to {backLabel}
            </a>
          ) : (
            <>
              <NavLink label="Overview" target="landing" />
              <NavLink label="Blog" target="blog" />
              <NavLink label="Docs" target="docs" />
            </>
          )}
        </nav>
        <div style={{ marginLeft: "auto", display: "flex", alignItems: "center", gap: 16 }}>
          {inApp ? (
            <span style={{ display: "flex", alignItems: "center", gap: 8, fontSize: 13, color: "var(--muted)" }}>
              <span
                style={{
                  width: 8,
                  height: 8,
                  borderRadius: "50%",
                  background: connected ? "var(--green)" : "#c9ccc6",
                }}
              />
              {connected ? "Engine connected" : "Connecting…"}
            </span>
          ) : (
            <button
              onClick={enterConsole}
              style={{
                fontFamily: "inherit",
                fontSize: 14,
                fontWeight: 600,
         
[truncated — 1156 more characters]
```

### web/vite.config.ts

```typescript
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

// Dev proxy: REST + WebSocket both go to the Node BFF on :8787.
export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      "/api": { target: "http://127.0.0.1:8787", changeOrigin: true },
      "/ws": { target: "ws://127.0.0.1:8787", ws: true },
    },
  },
});

```

### web/index.html

```html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>MacroWeaver — Generative socio-economic simulation</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=Spectral:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400&family=Hanken+Grotesk:wght@400;500;600;700&family=Spline+Sans+Mono:wght@400;500&display=swap"
      rel="stylesheet"
    />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

```

### presets/fish_calvano_monopoly.yaml

```yaml
# Fish et al. 2024 — MONOPOLY model-validation pre-step (run this BEFORE the duopoly).
# A SINGLE pricing firm on the P0 (base) prompt, Claude Haiku 4.5 @ temperature 1. The point of
# the paper's monopoly experiment: a model that can't even find and hold the optimal price as a
# monopolist won't produce a clean super-competitive signal in the duopoly. Run it ~3 times and
# confirm price converges to and holds the monopoly optimum.
#
# NOTE ON THE TARGET: with n=1 the engine's "monopoly" benchmark is the SINGLE-firm optimum
# (~1.78 at these params), NOT the 2-firm joint-monopoly 1.92 (joint monopoly internalizes the
# cross-product substitution and is higher). So validate against ~1.78 here. If you specifically
# want a 1.92 target, that requires one agent controlling BOTH products — ask and I'll add it.
#
# Live only (needs ANTHROPIC_API_KEY); use_cache off so the 3 runs are independent samples.
seed: 3
rounds: 200
run_name: fish_calvano_monopoly

market:
  type: fish_calvano
  params:
    a: 2.0
    mu: 0.25
    a0: 0.0
    cost: 1.0
    alpha: 1.0
    beta: 100.0

cohorts:
  - id: monopolist
    name: Monopolist
    n: 1
    persona: pricing manager for the only firm in the market
    policy: claude
    profile: { prefix: P0, cost: 1.0 }
    initial_state: { price: 1.50 }
    memory: notepad
    reflection: none

layers:
  observation: true
  news: false

scheduler:
  granularity: round
  reflect_every: 4

policy:
  model: claude-haiku-4-5
  temperature: 1
  use_cache: false
  max_concurrency: 1

```

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