Static site
The built constitution is also published as static web pages, English at
site/index.html and Chinese at site/zh/index.html, each with the
version for agents under agent/ (site/agent/index.html,
site/zh/agent/index.html), and so is this design, under design/
(site/design/index.html, site/zh/design/index.html). Hugo builds
them, hugo-0.167.0 -s web alone. CI runs three steps: uv run count-tokens (the token
count, below), hugo-0.167.0 --source web --cleanDestinationDir --panicOnWarning, and uv run check-site-anchors site, the
link check. The workflow .github/workflows/pages.yml publishes the
site to GitHub Pages at https://agent-constitution.luolc.com/, which it
passes to Hugo as --baseURL.
- Publishing: on each push to
main, and on a manual run, the jobbuildcounts the tokens (below), builds and checks the site as CI does and uploads it; the jobdeploypublishes it, but only when the repository is public and its variablePAGES_DEPLOYistrue, and is skipped otherwise. Publishing is a release (rulemust-ask-items): setting the variable is the person’s yes to publish every later push tomain, and deleting it stops that. The custom domain is set in the repository’s Pages settings, with the source “GitHub Actions”; a workflow that publishes ignores aCNAMEfile, so there is none. In the published build, links between the pages stay relative (relativeURLs); the canonical link, the language alternates andog:urlare absolute, on the domain.web/hugo.tomlkeepsbaseURL/: with an absolute one, the theme addsintegrityandcrossoriginto the style sheet link, and a page opened as a file loses its styles. - Input: the generated
constitution.<lang>.mdin the edition directory’sgenerated/(The constitution), not the sources: the citations, numbers and anchors are rendered once, byconstitution-build.web/hugo.tomlmounts them and the edition directory’sindex.tomlfromconstitution/as assets, and the partialweb/layouts/_partials/constitution/source.htmlfinds each by name, failing the build unless exactly one file matches; Python writes no page. The content adapterweb/content/_content.gotmplmakes each into the home page of its language: the header line is dropped, the title becomes the page’s, each heading’s anchor (## <a id="general-provisions"></a>Article I. ...) becomes the heading’s id, so the table of contents links to it, and the page gets the lengths (below) as params. It fails the build when a file does not start with the header line, a blank line and the#title. The layout (web/layouts/home.html) sets the title, the list under it and the text in<div class="constitution">, the root the style sheet hangs on. The title (web/layouts/_partials/constitution/title.html) has no#link from the theme and keeps its name and its parenthesis each on one line, so a narrow screen breaks it only before the parenthesis; its size is chosen so that it fits on one line in a desktop window 1400 pixels wide. The revision fromindex.tomlis followed by(draft)((草案)) while itsstatusisdraft. Its words, in both languages, are inweb/i18n/. Under the title is a list without bullets: the revision and the length (below), each centered under the title, and, when the text has an asterisk, its legend, starting at the column’s left edge, smaller and fainter than the two lines above it: the asterisk, then that the parts marked are background for people (where the constitution comes from, how it is organized and how it is amended) and that the version for agents, linked, omits them. The asterisk is set small and bold in the accent color, not a superscript and without opening up the line, lowered until its middle sits a little above the middle of an item number’s parentheses and its top well under the top of the number. The legend and the table of contents show it the same way. - Version for agents: each generated
constitution.<lang>.agent.mdbecomes the pageagent/under the home page of its language, made by the same content adapter (layoutweb/layouts/page.html) but with its text as is: no anchors, no links, the provisions written for human readers only their numbers and a pointer to the full version. Its headings carry{anchor=false}, so the theme adds no#link to them. Under the title is a list like the home page’s, both lines centered: the revision, and a link back to the full version. The sidebar, the language switch (to the other language’s version for agents), the color switch and the table of contents are as on the home page, the table of contents linking to the IDs Hugo gives the headings. - Design: the files of
docs/design/become the pages underdesign/of each language, read where they are:web/hugo.tomlmountsdocs/design/*.mdanddocs/design/zh/*.mdas assets, and the content adapterweb/content/design/_content.gotmplmakes the pages, so no copy is kept.README.mdis the pagedesign/itself, each other filedesign/<name>/, in the order of the README’s list of contents, whose link texts name the pages in the sidebar; the build fails when the list and the directory do not have the same files. Each file’s#title becomes the page’s. The English pages readdocs/design/, the Chinese ones the translation indocs/design/zh/: its first line, the SHA-256 of the English file (the design translation check in Checkers), is dropped, and each heading’s anchor,## <a id="checks"></a>检查, becomes the heading’s id, the same as the English heading’s. While a file has no translation, its Chinese page shows the English text, under a line saying that there is no translation yet (本页还没有中文译文,下面是英文原文。); the build never fails for a missing translation, and the Chinese sidebar names pages from the Chinese README, or the English one while it has none. The layout (web/layouts/design/all.html) sets the title and the text in<div class="design">, in the constitution’s fonts, size and column and with its colors, but as a document: headings at the left, the theme’s bullets, tables and code blocks. The table of contents lists the level-2 headings, as on the constitution’s pages; the design has no deeper ones today. - Links in the design: written for the files as GitHub shows them, they
are rewritten for the site by the link render hook
(
web/layouts/_markup/render-link.html), and the sources stay as they are. A relative link is resolved against the directory of the file the page read,docs/design/ordocs/design/zh/, so a translation writes a link to another design file as the English does (rules.md#rules) and a link out ofdocs/design/with one more../. A link to a design file, in either directory, goes to its page in the page’s language; to a generated constitution,constitution/<edition>/generated/constitution.<lang>.md, to the home page of that language; to any other file of the repo, to the file on GitHub (params.SourceRepo,/blob/main/). The fragment is kept. A link to a design file without a page, or one that leads out of the repo, fails the build; a link to a file that does not exist is left to the design translation check. Links on the constitution’s pages, and absolute ones and fragments alone everywhere, are left as written. - Length: the second item of the list gives the length of the version
for agents, bare, which is what takes room in an agent’s context: the
generated file without its header line, in characters in both
languages, words split on white space (ASCII white space, the only kind
the files hold) in English, and tokens as counted for the model
params.tokenCount.modelofweb/hugo.tomlby Anthropic’s count_tokens API (POST /v1/messages/count_tokens, standard library only). The content adapter counts the characters and words;count-tokenscounts the tokens and writes them toweb/data/lengths.json(not committed), with the SHA-256 of each text it counted and the full SHA of the commit it ran on. The site shows a token count, and the commit, only when the SHA-256 is that of the text it builds, so a file left from another commit shows no stale number; without the file it says the tokens were not counted. An info button after it opens a popup, on hover or a click or tap; it is a disclosure button (aria-expanded), reached with the keyboard, and Escape closes it (web/assets/count-info.js, inlined in<head>). The popup hangs from the button with no gap, centered under the length and at least as wide as it, so the pointer reaches its link without leaving it; on a narrow screen it spans the column. The popup first says that the length it hangs under is that of the version for agents and gives the lengths of both versions in a small table (spans with ARIA table roles, so a screen reader names the columns; the full version is the generated file without its header line, anchors and link definitions included; a token count not made is a dash), the table as wide as the popup; under it, fainter than the table, a bulleted list (spans with ARIA list roles): what the version for agents omits, which API counted the tokens for which model or why they were not counted (the model named in words byparams.tokenCount.namenext to its ID), and the revision and the commit of the count, abbreviated to seven characters and linked to that commit in the source repository, opened in a new tab (left out without a count of this text, or outside git); last, on a line of its own, the link to the page of the version for agents. The text of the popup is the size of the legend under the title, the numbers of the table a little larger. Each page’s two versions are counted separately, so a build with a credential makes four calls. - Credential: only from the environment, never from a file in this repo,
in one of two variables.
CONSTITUTION_TOKEN_COUNT_API_KEYholds an API key, sent asx-api-key.CONSTITUTION_TOKEN_COUNT_ACCESS_TOKENholds a short-lived access token that a workflow got through Anthropic’s Workload Identity Federation (its OIDC token exchanged atPOST /v1/oauth/token), sent asAuthorization: Bearer; when both are set, the token is used, so a key left behind does not shadow the federation. The credential never reaches the output, the logs or the page; a failure prints one line with the HTTP status or the exception’s class. Without a credential, or when the call fails or a connect or read stalls for 10 seconds, the page says the tokens were not counted and the build goes on; withcount-tokens --require-tokensthe count fails instead and writes no file. The jobbuildof.github/workflows/pages.ymlruns only onmain(the federation rule accepts a run on any branch of this repository, not onlymain; the repository chooses to publish onlymain) and in no environment, which would change the token’s subject: it asks GitHub for the run’s OIDC token (audiencehttps://api.anthropic.com), exchanges it for an access token with the IDs of the federation rule, organization, service account and workspace, which are repository secrets (ANTHROPIC_FEDERATION_RULE_ID,ANTHROPIC_ORGANIZATION_ID,ANTHROPIC_SERVICE_ACCOUNT_ID,ANTHROPIC_WORKSPACE_ID; identifiers, not credentials, but the logs of a public repository are open to anyone and the log masks a secret as***, where it prints a variable), counts the tokens with--require-tokens, builds and checks the site as CI does, and writes the length line of both pages into the job summary and into the log, which, unlike the summary, the API can read. A failed exchange or count fails the run, and nothing is published. The CI on pull requests gets no credential, and neither does a pull request from a fork. A local build that wants the count sets the key variable for that one command, reading the key from the maintainer’s secret store. So the pages of one commit differ with and without a credential, in that line and the popup only. - Generator: Hugo, a single binary pinned by version in the workflows
and in
tools/site/tests/test_site.py, which writes tosite/(publishDir), with the theme hugo-book (MIT license) as a git submodule atweb/themes/hugo-book, pinned to a commit. The theme gives the language switch, the light, dark and auto color themes, the sidebar’s list of pages and the table of contents at the right. The sidebar lists the pages, not the articles, in groups named inweb/hugo.toml’s menus: each group a top-level entry, its name in bold after an icon of its own from Octicons (params.BookIcon, a file inweb/assets/icons/), its pages as children. There are two groups: the constitution, the home page, with the scales (law), and the design, its README’s page, with an open book (book), its files below it as the content adapter lists them (Design, above). The design group folds (collapsein its menu params): it is closed on every page but the design’s own, where it is open with the current page marked. It uses the theme’s checkbox, label and chevron from its file tree; the chevron points right when closed and down when open, and the keyboard toggles it. The constitution group does not fold. Under the constitution are its editions, newest first: the content adapter adds each page’s entry to the menu, named fromindex.tomlas “2026-10 Edition” / 「2026-10 版」, followed by “(draft)” / 「(草案)」 whilestatusisdraft, and weighted by the edition’s year and month, negated. Today that is the one edition, the home page; a page per edition, once there are several, is not built yet. The version for agents is not listed, as it is not meant for people to read; the home page links it (the legend and the popup on the length), and the content adapter hides it from the theme’s own list withbookHidden, as it hides the design’s pages, which the menu lists. The table of contents lists the page’s level-2 headings, the preamble and the articles (markup.tableOfContentsinweb/hugo.toml), under a heading of its own; on a narrow screen the theme hides it behind a button at the right of the top bar, which opens it above the text, as the button at the left opens the sidebar. It marks the article the reader is in (aria-current), set byweb/assets/toc-current.js, inlined in<head>: the last heading above a line at 30% of the window’s height, or the last one once the end of the text is in view, watched withIntersectionObserver, which every current browser has; nothing is read on scroll. CSS alone (scroll-target-groupand:target-current) did this only in Chromium when read (2026-10-13). The site overrides the theme’s sidebar (layouts/_partials/docs/menu.html) to put a link to the source repository, with GitHub’s mark from Octicons (MIT license), between the site’s title and the language switch, and adds, through the theme’s inject partials, a switch for the color theme at the right end of the language switch’s row, and the fonts. The color switch is one button that cycles through following the system, light and dark, as Docusaurus’s does; it shows the current setting by an Octicons icon (device-desktop,sun,moon), and its title and accessible name give the setting and the next one, fromweb/i18n. The choice is kept inlocalStorageand set in<head>before the page is drawn. A submodule keeps the theme’s files out of this repo’s diffs; a Hugo module would need Go on every machine that builds. - Raw HTML: Goldmark’s
unsaferenderer is on, since the anchors (<a id="...">) and the asterisks are raw HTML. The content is only this repo’s own built constitution and design, never outside input. - Anchors: the ID of the article, section or item:
#general-provisions,#terms,#terms.principal, the same in both languages and in the Markdown. A citation is a reference link to it.:targetflashes the element an anchor starts. - Style:
web/assets/styles/custom.css, loaded by the theme after its own. Its first part uses only the page’s own markup and moves to another generator as is; the last part maps it onto hugo-book’s variables. Centered article headings, bold section and item numbers (bold in the Markdown), English text ragged right and Chinese justified, a print style without link decoration. Every link in the text, under the title and in the popup is underlined like the citations: the text color with a light underline in the color of the rules, both turning to the accent color under the pointer. The navigation (the sidebar, the table of contents and the top bar of a narrow screen) is not underlined, but is under the pointer or the keyboard’s focus. The sidebar marks the current page in the accent color. The table of contents is set apart from the text, muted along a thin rule; its current article is bold, in the accent color, with a bar over the rule, so it does not rest on the color alone. The text size grows with the window between a floor and a ceiling (clamp()), and the column is set in em, so a line keeps about 90 characters in English (36em, measured on the text’s full lines: EB Garamond’s letters are narrower thanch; the usual 75 reads too narrow for this text) and 40 in Chinese where the window has room for it. Three columns would leave the text under about 65 characters a line in English below 70rem (1120px), so there the table of contents goes behind the theme’s top-bar button, floating at the top right, which opens it over the text at the right edge; the sidebar stays down to hugo-book’s breakpoint (56rem), below which the theme hides it too. The column is centered between the sidebar and the table of contents. Fonts: EB Garamond and Noto Serif SC (SIL Open Font License 1.1) from Google Fonts as variable fonts, falling back to local serif fonts. The colors are one palette block, light and dark; contrast of text on the page and the sidebar is above WCAG AA in both, the values are in the style sheet. - The few Chinese strings of the site itself (
web/i18n/zh.yaml, which holds the labels and the popup text, and the language label and menu names inhugo.toml) are outside limae’s reach. site/is not committed: a generated page under review would make every amendment touch it, while an amendment may change only the constitution sources and their generated Markdown. Hugo needs a download, so the build is not a commit hook, and the tests of the templates (test_site.py, which builds a made-up constitution and design) are skipped without it, except in CI; CI installs Hugo before the tests, builds the site on every pull request and push and runscheck-site-anchorson it.