From b40c0e8ba56a1a094334602f143ff3ceb2414df0 Mon Sep 17 00:00:00 2001 From: Sushant Gautam Date: Tue, 3 Nov 2026 23:48:17 +0100 Subject: [PATCH] chore: move website code off main The site now lives on the separate `website` branch (no shared history), which deploys https://simulamet.github.io/Verdi/. main keeps only links to it. --- .github/workflows/pages.yml | 44 ------ site/build.py | 286 ------------------------------------ 2 files changed, 330 deletions(-) delete mode 100644 .github/workflows/pages.yml delete mode 100644 site/build.py diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml deleted file mode 100644 index 5bb7b2b..0000000 --- a/.github/workflows/pages.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Website - -# Builds the GitHub Pages site (https://simulamet.github.io/Verdi/) from -# site/build.py and docs/*.md whenever they change on main. -on: - push: - branches: [main] - paths: ["site/**", "docs/**", "pyproject.toml", ".github/workflows/pages.yml"] - workflow_dispatch: - -permissions: - contents: read - pages: write - id-token: write - -concurrency: - group: pages - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: Build site - run: | - pip install markdown - python site/build.py - - uses: actions/upload-pages-artifact@v3 - with: - path: _site - - deploy: - needs: build - runs-on: ubuntu-latest - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - id: deployment - uses: actions/deploy-pages@v4 diff --git a/site/build.py b/site/build.py deleted file mode 100644 index f708c25..0000000 --- a/site/build.py +++ /dev/null @@ -1,286 +0,0 @@ -"""Build the GitHub Pages site (landing page + guides) into ``_site/``. - - pip install markdown - python site/build.py # writes _site/ - python -m http.server -d _site # preview - -The guides are rendered from ``docs/*.md`` so the site never drifts from the -repo. Every page gets a canonical URL, description, Open Graph tags and -structured data; ``sitemap.xml`` and ``robots.txt`` let search engines find -all of it. -""" -from __future__ import annotations - -import html -import json -import re -import shutil -from datetime import date -from pathlib import Path - -import markdown -import tomllib - -ROOT = Path(__file__).resolve().parents[1] -OUT = ROOT / "_site" -SITE = "https://simulamet.github.io/Verdi/" -REPO = "https://github.com/SimulaMet/Verdi" -PYPI = "https://pypi.org/project/verdi-agents/" -VERSION = tomllib.loads((ROOT / "pyproject.toml").read_text())["project"]["version"] - -# docs/.md -> /.html, with a search-friendly title and description. -GUIDES = [ - ("SWEEPS.md", "sweeps", "Sweeps: compare LLMs and settings across seeds", - "Rerun one agent-society scenario across models, governance settings and seeds " - "with one YAML file. Plan and cost first, resume, spreadsheet and comparison page."), - ("SCENARIOS.md", "scenarios", "Scenario reference", - "Every key of a Verdi scenario YAML: model endpoint, agents, resources, shocks, " - "constitution, economy, delivery tasks, interviews and probe schedule."), - ("RESEARCHER_GUIDE.md", "researcher-guide", "Researcher guide: paired institutional study", - "Run a fixed-vs-revisable institutions experiment on LLM agent societies with one " - "config file and read the comparative results."), - ("INSTITUTIONAL_RECOVERY.md", "institutional-recovery", - "Institutional recovery experiments", - "How Verdi measures whether societies of LLM agents recover from shocks under fixed " - "or amendable constitutions, using objective delivery tasks."), -] - -CSS = """ -:root{--bg:#fbfbfd;--surface:#fff;--fg:#1d1d1f;--muted:#5f6368;--line:#e4e4e9; ---accent:#2f5bd3;--accent-soft:#eef2fd;--code:#f4f4f7;--radius:14px} -@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg:#0f1115; ---surface:#161a20;--fg:#ecedf0;--muted:#a1a6b0;--line:#272c35;--accent:#8aa8ff; ---accent-soft:#1a2236;--code:#1b2027}} -*{box-sizing:border-box}html{-webkit-text-size-adjust:100%} -body{margin:0;background:var(--bg);color:var(--fg); -font:16px/1.65 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif} -a{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline} -.wrap{max-width:1040px;margin:0 auto;padding:0 20px} -header.top{border-bottom:1px solid var(--line);background:var(--surface)} -header.top .wrap{display:flex;align-items:center;gap:20px;height:60px} -.brand{font-weight:700;font-size:18px;color:var(--fg)} -nav{display:flex;gap:18px;font-size:15px;white-space:nowrap;overflow-x:auto; -scrollbar-width:none}nav a{color:var(--muted)} -nav a:hover{color:var(--fg);text-decoration:none} -.spacer{flex:1} -.hero{padding:72px 0 40px}.hero h1{font-size:clamp(34px,6vw,54px);line-height:1.1; -margin:0 0 16px;letter-spacing:-.02em}.hero p.lead{font-size:clamp(17px,2.4vw,21px); -color:var(--muted);max-width:720px;margin:0 0 28px} -.cta{display:flex;gap:12px;flex-wrap:wrap} -.btn{display:inline-block;padding:11px 18px;border-radius:10px;font-weight:600; -border:1px solid var(--line);color:var(--fg);background:var(--surface)} -.btn.primary{background:var(--accent);color:#fff;border-color:var(--accent)} -.btn:hover{text-decoration:none;filter:brightness(1.05)} -pre,code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:14px} -pre{background:var(--code);border:1px solid var(--line);border-radius:var(--radius); -padding:16px 18px;overflow-x:auto;line-height:1.55} -:not(pre)>code{background:var(--code);padding:2px 6px;border-radius:6px} -section{padding:36px 0}section h2{font-size:28px;margin:0 0 8px;letter-spacing:-.01em} -section p.sub{color:var(--muted);margin:0 0 24px;max-width:720px} -.grid{display:grid;gap:16px;grid-template-columns:repeat(auto-fit,minmax(240px,1fr))} -.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius); -padding:20px}.card h3{margin:0 0 6px;font-size:17px}.card p{margin:0;color:var(--muted); -font-size:15px} -.steps{counter-reset:s;display:grid;gap:16px; -grid-template-columns:repeat(auto-fit,minmax(280px,1fr))} -.steps .card h3::before{counter-increment:s;content:counter(s);display:inline-grid; -place-items:center;width:26px;height:26px;margin-right:8px;border-radius:50%; -background:var(--accent-soft);color:var(--accent);font-size:14px} -article.doc{max-width:820px;padding:40px 0 64px} -article.doc h1{font-size:clamp(28px,4.5vw,38px);line-height:1.2;letter-spacing:-.01em} -article.doc h2{margin-top:2em;padding-top:.4em;border-top:1px solid var(--line)} -article.doc table{border-collapse:collapse;display:block;overflow-x:auto;margin:1em 0} -article.doc th,article.doc td{border:1px solid var(--line);padding:6px 10px; -text-align:left;vertical-align:top}article.doc th{background:var(--code)} -.crumbs{font-size:14px;color:var(--muted);margin-bottom:8px} -footer{border-top:1px solid var(--line);padding:28px 0 40px;color:var(--muted);font-size:14px} -footer .wrap{display:flex;gap:16px;flex-wrap:wrap;justify-content:space-between} -@media (max-width:760px){nav.ext,.spacer{display:none}.hero{padding:44px 0 24px}} -""" - - -def page(*, title: str, description: str, path: str, body: str, - jsonld: dict | None = None) -> str: - url = SITE + path - nav = "".join(f'{t.split(":")[0]}' - for _, s, t, _ in GUIDES[:2]) - ld = f'' if jsonld else "" - e = html.escape - return f""" - - -{e(title)} - - - - - - - - -{ld} - -
-
{body}
-
-Verdi {VERSION} · MIT license · by Sushant Gautam, SimulaMet -GitHub · PyPI · -Issues -
- -""" - - -def landing() -> str: - feats = [ - ("Auditable by construction", "Every action, vote, law, sanction and appeal is written " - "to an append-only event log and replayed to an identical state hash."), - ("Declared vs observed values", "Agents state their values; Verdi measures what they " - "actually do. The gap over time is the measurement."), - ("Self-governing societies", "Universal laws plus a constitution agents can amend in " - "assemblies: quorum, thresholds, sanctions, appeals, restoration."), - ("Shocks and recovery", "Deterministic exogenous shocks, a constrained economy and " - "objective delivery tasks show whether a society recovers."), - ("Compare models with sweeps", "Vary the model, quorum or shock size in one YAML file; " - "every combination runs on the same seeds, in parallel, resumable."), - ("One-click reports", "Each run renders a self-contained report.html; each sweep a " - "comparison page and a spreadsheet."), - ] - cards = "".join(f'

{t}

{d}

' for t, d in feats) - body = f""" -
-
-

Verdi: an auditable testbed for societies of LLM agents

-

Run a society of language-model agents over time, let them govern -themselves, hit them with shocks, and measure how their values and institutions -drift and recover. Reproducible, seeded, and fully logged.

- -
- -

Install in one line

-

No virtual environment needed: uv -fetches Verdi on first use. Works with any OpenAI-compatible endpoint (vLLM, Ollama, -hosted APIs).

-
uvx verdi-agents init my-study          # example scenarios, sweeps, .env template
-cd my-study                             # add your key to .env, endpoint to examples/starter.yaml
-uvx verdi-agents run examples/starter.yaml
-open runs/starter/report.html
-

Or pip install verdi-agents, then use the verdi command.

-
- -

What Verdi measures

-

Built for research on AI safety, alignment and governance in multi-agent -LLM systems.

-
{cards}
- -

How it works

-
-

Describe a society

One YAML file: the model endpoint, -agents with roles and declared values, resources, laws, constitution, shocks and -when to interview them. See the scenario reference.

-

Run it

verdi run scenario.yaml drives every -agent through every tick, validates each action against the law, and writes the -full event ledger.

-

Compare

verdi sweep reruns the scenario -across models and settings and summarizes the outcomes per condition. -See sweeps.

-
- -

Compare LLMs on the same society

-

Change only the model; everything else stays identical.

-
name: model_comparison
-scenario: examples/full.yaml
-vary:
-  model.name: [model-a, model-b]
-parallel_runs: 2
-
uvx verdi-agents sweep examples/sweeps/model_comparison.yaml --dry-run   # plan + cost
-uvx verdi-agents sweep examples/sweeps/model_comparison.yaml             # run, resumable
-
- -

Guides

-{"".join(f'

{html.escape(t)}

{html.escape(d)}

' for _, s, t, d in GUIDES)} -
-
""" - jsonld = { - "@context": "https://schema.org", "@type": "SoftwareSourceCode", - "name": "Verdi", "alternateName": "verdi-agents", - "description": DESCRIPTION, "url": SITE, "codeRepository": REPO, - "programmingLanguage": "Python", "license": "https://opensource.org/licenses/MIT", - "version": VERSION, "author": {"@type": "Person", "name": "Sushant Gautam"}, - "sourceOrganization": {"@type": "Organization", "name": "SimulaMet"}, - "keywords": "LLM agents, multi-agent simulation, AI safety, AI governance, " - "value drift, institutional design, agent-based modeling", - "runtimePlatform": "Python 3.10+", "downloadUrl": PYPI, - } - return page(title="Verdi: auditable testbed for societies of LLM agents", - description=DESCRIPTION, path="", body=body, jsonld=jsonld) - - -DESCRIPTION = ("Open-source Python testbed for societies of LLM agents: self-governance, " - "sanctions, shocks and recovery, with auditable logs, reproducible runs and " - "model comparison sweeps.") - - -def _fix_links(text: str) -> str: - slugs = {f: s for f, s, _, _ in GUIDES} - - def repl(m: re.Match) -> str: - target = m.group(1) - name = target.split("#")[0] - anchor = target[len(name):] - if name in slugs: - return f"]({slugs[name]}.html{anchor})" - if name.startswith("../"): - kind = "tree" if name.endswith("/") else "blob" - return f"]({REPO}/{kind}/main/{name[3:]}{anchor})" - if name.endswith(".md") and "://" not in name: - return f"]({REPO}/blob/main/docs/{name}{anchor})" - return m.group(0) - - return re.sub(r"\]\(([^)\s]+)\)", repl, text) - - -def guide(src: str, slug: str, title: str, description: str) -> str: - text = _fix_links((ROOT / "docs" / src).read_text(encoding="utf-8")) - body_html = markdown.markdown(text, extensions=["tables", "fenced_code", "toc", "sane_lists"]) - body = (f'
') - jsonld = {"@context": "https://schema.org", "@type": "TechArticle", "headline": title, - "description": description, "url": SITE + f"{slug}.html", - "isPartOf": {"@type": "WebSite", "name": "Verdi", "url": SITE}} - return page(title=f"{title} | Verdi", description=description, - path=f"{slug}.html", body=body, jsonld=jsonld) - - -def main() -> None: - if OUT.exists(): - shutil.rmtree(OUT) - OUT.mkdir() - (OUT / "index.html").write_text(landing(), encoding="utf-8") - urls = [SITE] - for src, slug, title, desc in GUIDES: - (OUT / f"{slug}.html").write_text(guide(src, slug, title, desc), encoding="utf-8") - urls.append(SITE + f"{slug}.html") - today = date.today().isoformat() - (OUT / "sitemap.xml").write_text( - '\n' - '\n' - + "".join(f" {u}{today}\n" for u in urls) - + "\n", encoding="utf-8") - (OUT / "robots.txt").write_text(f"User-agent: *\nAllow: /\nSitemap: {SITE}sitemap.xml\n", - encoding="utf-8") - (OUT / ".nojekyll").write_text("", encoding="utf-8") - print(f"Built {len(urls)} pages into {OUT}") - - -if __name__ == "__main__": - main()