Imported from TheDoshus/doshusweb (
AGENTS.md). Install upstream withnpx skills add TheDoshus/doshusweb. Copyright stays with the author.
AGENTS.md — doshusweb
Cross-agent conventions for doshus.net. Governs how any coding agent — Claude Code, Zephyy, Codex, Opencode, or any model you bring — works on this codebase. Vanilla HTML/CSS/JS on Firebase Hosting: no frameworks, no build step; public/ is served as-is. See DOSHUS.md for deploy workflow and BLUEPRINT.md for architecture/roadmap.
Single source of truth. These are the project rules for every agent, and there is no
CLAUDE.md(Doshus, 2026-10-10). Claude Code (v2.1.277+) reads this file itself when noCLAUDE.mdsits in the working directory or above it. Under~/.openclawone does (OpenClaw's own), so on a machine with this repo there,~/.claude/settings.jsonneeds"pluginConfigs": {"cc-plugin-agents-md@builtin": {"options": {"instructionFiles": "claude-md-and-agents-md"}}}(or/config→ Project instructions), which loads both and never reads oneAGENTS.mdtwice; project settings can't set it (Claude Code docs § AGENTS.md, read 2026-10-10). Gemini CLI reads onlyGEMINI.mdunless told otherwise: point it here with"context": {"fileName": ["AGENTS.md"]}in itssettings.jsonrather than keeping a copy. VS Code needs no file of its own: its Copilot, Codex and Local harnesses readAGENTS.md(chat.useAgentsMdFile, default on), so a.github/copilot-instructions.mdwould only be a second copy; its own Claude harness reads onlyCLAUDE.md(VS Code docs approved 2026-09-17), so it doesn't get these rules. The one exception is the generated build-standards block below: it comes from the OpenClaw rootAGENTS.md, which also governs anything done from the OpenClaw house itself (Zephyy's identity, confirming external actions). Everything after that block is doshusweb's own. Deploys, pushes to other repos and anything that leaves the machine still need Doshus's OK.
How Doshus Wants Things Built
Doshus's standards for everything an agent or operator builds or changes, in every project: code, config, prompts, hooks, docs and UI. They are the standing bar, not requests. "Rule N" below means the numbered coding Rules, which say how to meet them.
The build bar
- Futureproof. Build so the next agent, model, machine or data source plugs in without rewriting what is already there: derive, don't copy (Rule 12). Anything that depends on a version, model, quota or upstream says so when it drifts, before it breaks.
- Modular. Each part has one clear job and a defined interface, so it can be swapped out, or backed by another clean path, without touching the rest: "a generic standard is better than a custom ragdolled one." Modular is organized, not scattered: extend the part shaped like your change before adding another (Rule 18). A pile of small scripts splitting one job is not modular.
- Lean and optimized. "The meanest and leanest code possible. I believe less code is the best code." Lean over clever, native features first, the fewest moving parts, no bloat in code, prose or reports: a report states the finding, not the scan (Rule 16). Optimizing never costs a capability: "I'm not here to lose functionality but improve on it."
- One source. Every fact, rule, helper and template has one home, and everything else points at it. Where a harness cannot point, the copy is generated from the source and checked for drift. A hand-kept copy is allowed only when it forks into something specialized for one agent, harness or situation, and it names what it forks from.
- Portable. It must survive a teleport: "I don't wanna boot up on another machine with nothin workin." Nothing machine-specific is baked into code or docs: users, homes, paths, ports and hosts come from config or the environment, or are derived (Rule 4). Build it generic enough to share, with our specifics layered on top.
- Continuous. When you learn or land something, update the memory, daily log, doc, handoff or board it changes right then, not at the end. Saying you will is not doing it.
- oklch() colors only. Every color value: CSS, tokens, inline styles, SVG
fillandstroke, generated colors, palettes, design specs, and any prompt you write for a tool that produces colors. No hex, rgb/rgba or hsl wherever oklch is available; a vendor example is a reference, not an exception. - Smart value first. Anything with more than one way to get it done (models, search, scraping, APIs, services) runs the best free option first, then the next best, and a paid one only as the true last resort. The order lives in the config that routes it, not only in prose: a doc that says "free first" over a config that picks paid is how every web search here ran on paid providers for months.
- Symmetric. "Having similar things built symmetrically... keeps operations clean and smoov" (2026-10-18). Like parts (agents, crew backends, gateways, crons, checkers) share one shape and the same capabilities, and we choose per use when each is on. A difference between them is either written beside it with its reason or it is a finding; fix it at the boundary (sandbox, hook, config), not by withholding a tool from one member.
How the work gets done
- Ask when you don't know or aren't confident. "Guessing does none of us any good." A question costs one message; a wrong guess costs the build and his trust.
- Verify, don't assert. Quote the output that proves it. A check that ran is not a check that found anything (Rule 15), and one that could not look reports UNKNOWN, never clean. Size the check to the blast radius: no unrequested repo-wide sweep for a local edit, and the wider audit when real dependencies or he calls for it.
- Gold-star it before moving on. Research it and think it through, then finish: the checks pass, the cleanup is done, the note is filed. Only then start the next thing.
- Read the manual before you touch a part of the system. Each part has a page: find it by the part's name and config keys and read it before the change, not after; a part with no page gets one in the same change.
- Latest and greatest, done properly. Prefer the maintained successor and the native feature over a superseded or hand-rolled one.
- Build to the domain's published standard. Where a spec or vendor best-practice guide exists (MCP, agent skills, hooks, an API), mirror it and build to it; our domain page holds only our deltas, each with its reason.
- Track what you install. A new tool, CLI or package gets a line in the tooling inventory and a manual the same session, or it rots unseen.
- Read the changelog of every update you come across. "They're new updates for a reason. We gotta eat em up" (Doshus, 2026-10-16). Installing, running or just spotting a new version of anything (a platform, a CLI, a package, a model): read its shipped changelog or the notes at its tag, never a summary, for what we hand-rolled that it now does natively, what changed default-ON, and what new capability we can use. Tell Doshus what's new; each item worth adopting becomes a card on the board.
- Call out mess when you see it: duplication, stale docs, dead files, and builds kept only because redoing them is work. Don't route around it.
- Answer every part. Keep every field he asked for and never drop one for brevity.
- Agents get capabilities, not caution. If the house already has a way to do a job (a script, a tool, a CLI), the agent uses it. A gate or "do not" an operator invents goes to Doshus as a proposal, never into an agent's files as a rule: "I don't need or want such baby restrictions on my agents" (2026-10-11). The Safety Boundaries below still hold.
- Two sets of eyes, preferably two models. Every change gets an independent review, by a reviewer following the reviewing standard. Use a different model where a free lane is available, and every finding is fixed or answered; a reviewer he names is part of done, and a command he runs as root is reviewed before he gets it. Short on review quota, routine work lands with its review owed and sharp edges wait. Timing and the kept verdict: Rule 7 § E.
The messy trail is the failure to guard against: scratch files left behind, a run-once script left in the script store (Rule 18), notes unfiled, board cards not moved, decisions not written down. Leave it clean and organized; before you stop, ask what you left behind.
Hard rules
- oklch only (build bar 7) — here black is
oklch(var(--space-oled)), white isoklch(var(--star-white)). - Use the token system in
public/css/shared.css:- Tier 1 primitives hold raw
L C Htriples (e.g.--brand-purple: 55% 0.28 290) — always consumed asoklch(var(--token))oroklch(var(--token) / alpha). - Tier 2 semantic tokens:
--accent-finance,--accent-crypto,--accent-taxes,--accent-invest,--accent-networth,--accent-lounge,--accent-amzn,--accent-myth,--accent-discord.--text-main/--text-mutedare full colors — use asvar(--text-muted), never re-wrapped inoklch().
- Tier 1 primitives hold raw
- Accent routing pattern for per-section theming: one custom property set per scope, shared rules consume it. Existing examples:
--sec(finance.css),--node-accent(nexus.css),--pill-accent(home.css),--swap-accent(printmon swapbtn.css — per-theme-page button,--pm-hue1fallback themes generated pages). Extend this pattern; don't copy-paste per-section rule blocks. - CSP is strict, and stays that way (Doshus's standard for every agent; A+ 110 on Observatory, 2026-09-26). No
'unsafe-*', wildcard or bare-scheme sources unless absolutely necessary; the standing exceptions are named inCSP_EXCEPTIONSinscripts/check.js(style-src 'unsafe-inline'because the widgets need it, already tested). Inline scripts get a hash viacsp:hashes, never a looser policy; no inlineon*=handlers orjavascript:URLs: the browser refuses them, so they are dead code. Adding any external fetch/iframe/script requires updating the CSP headers infirebase.json— in both hosting targets (mainandzephyy). Scope to the tightest path that works (e.g.https://discord.com/widget, nothttps://discord.com). - Fonts are self-hosted woff2 in
public/assets/fonts/— no Google Fonts requests. Each face is named once, as a--font-*token inshared.css(body, button, h1, h3, hero, title, accent, card, mono, panel); everything else uses the token, so trying a new font is one line. The one exception, commented: the Printmon dock (zephyy-orb-dock.css) uses Amazon Ember, the internal site's font, which can't be hosted here. - The board is the page layout (Doshus, 2026-10-10: "new standard like oklch"). New pages and sections, and any page being reworked, are a board of panels per
UI-SPEC.mdonboard.css+board.js; no new bespoke card grids. Pages convert in the order ofBLUEPRINT.md§ Board rollout; until a page's turn, build its new content panel-shaped (one self-contained block: head, body, foot) so converting it is a wrap, not a rewrite.
Layout
| Path | What |
|---|---|
public/*.html + css/ + js/ |
Main site (home, financehub, thelounge, nexus, zephyy) |
public/css/shared.css |
Tokens, fonts, cosmic background, shared components |
public/js/main.js |
Global: WebGL sky (the drifting nebula, then the star field; colors are the --nebula-* and --star-* tokens) + shooting stars, meme loader, collapsibles, sticky footer |
public/css/board.css + public/js/board.js |
The board engine (UI-SPEC.md); on /nexus (the first), /financehub, /thelounge and / |
public/zephyy/ |
Zephyy profile subpages |
public/amazon/ |
Printmon + work tools — hands off unless Doshus says so (Doshus, 2026-10-10); he hand-syncs it to an internal host (AMZN-INTERNAL-SYNC.md) |
public/assets/memes/ |
Meme pool; regen index with bun run memes; shrink new ones with bun run memes:convert (quality-gated, report first); each within its budget (scripts/lib/meme-budget.js, held by bun run check) |
firebase.json |
Hosting config + CSP/security headers (two targets) |
scripts/ |
Repo tooling, run through bun (bun run <name>): check, csp:hashes, memes, memes:convert, sync:zephyy (re-stamp the Zephyy subpages after editing public/zephyy.html) |
Conventions
- Two kinds of code, two bars (Doshus, 2026-09-26). Zephyy's surface is agent-written —
zephyy.html,public/zephyy/,public/css/zephyy-*.css,public/js/zephyy-*.js— and agents may clean it up without line-by-line review. Everything else is Doshus's hand-crafted code: recommend improvements freely, but show him the smallest diff and get his approval before it is committed. On both, fewer lines for the same behavior wins: skipping review is not skipping the bar. - Zephyy's surface rides the site; it doesn't fork it (Doshus, 2026-10-10). Her pages use the same sky (
main.js), tokens,haptic()and shared components as his, and add only what is hers: chat, realtime status, her glyph. Extend thezephyy-*file shaped like the change (one script per job,zephyy.cssas the base under per-page sheets) instead of adding a file, and never copy a shared rule or helper into her sheets. - Grep before you write. Before adding a new function, CSS token, util, or component, search
public/for one that already does the job (grep -rn "formatDate\|--accent-" public/) — reuse or extend it, never spawn a parallel. A second date-helper or a duplicate token is a bug. This site's token + accent-routing system exists to be extended, not copy-pasted. - JS is vanilla: guard for missing DOM elements (scripts are shared across pages), build user-facing strings with
createElement/textContent(not innerHTML), keep console quiet in production paths. - No
?v=cache-busters on css/js references (Doshus's call 2026-07-16). css/js are servedCache-Control: no-cache(2026-10-10): browsers revalidate each load and get a 304 when nothing changed, so a change shows on the next load. A script whose new version can't run beside an old cached copy of another still gets a new file name in that deploy. Everything under/assets/(fonts, images, memes, vendored video.js) isimmutablefor a year, its rule after the css/js one infirebase.jsonso it wins: change one of those by giving it a new name (memes:convertre-encoding a meme in place is the exception: a visitor still holding the old copy holds the same meme). The one file there rebuilt in place,assets/memes/meme-list.json(bun run memes), getsno-cachefrom a rule after that, so a new meme reaches returning visitors. - Respect
prefers-reduced-motionfor any new animation (CSS override exists in shared.css; JS checksprefersReducedMotionin main.js). - Random meme containers: any
.random-meme-fixeddiv (a media panel's slot) gets auto-filled by main.js frommeme-list.json. - Pictures in markup carry their file's
widthandheight(its pixel size; the browser holds that shape before the file arrives), andloading="lazy"unless they're in the first screen. CSS that sets an image's width setsheight: autowith it, or theheightattribute becomes its height.bun run checkflags an<img>without them (Doshus, 2026-10-17).
Verify before committing
bun run check # read-only; exits 1 on any failure
bun run test # chat client + board engine in Chromium (Playwright)
bun run serve # eyeball locally: public/ at Firebase's addresses (clean URLs, redirects, 404.html)
bun run check (scripts/check.js) runs: JS syntax (every .js/.cjs), every JSON file parses, CSS brace balance (browsers won't error on a missed }; it silently eats rules), oklch-only on public/ CSS, HTML <style>/style=/color attributes, JS color strings and SVGs (Printmon and vendor/ exempt; pre-lint SVGs are a baseline list that only shrinks), the CSP staying strict (both targets identical, no unsafe/wildcard sources outside CSP_EXCEPTIONS, no inline handlers or javascript: URLs outside Printmon), and drift: csp:hashes and sync:zephyy in --check mode must find nothing to change. A FAIL on drift means run that generator and commit the result. It also checks every board's markup (board names unique across the site, panel ids unique, cells inside the grid, no overlapping defaults), that a page with a Printmon orb dock links its sheet, that every picture in a page's markup carries its width and height, that a script from another site pinned to a version carries its integrity hash, that every meme is within its budget (scripts/lib/meme-budget.js; the heavy ones from before it are a baseline list that only shrinks, each held to the size it had), and that public/.well-known/security.txt is current (a Contact URI, one Expires between 30 days and a year out, its Canonical address): it goes red 30 days before the file expires. Every deploy runs it first (firebase.json's predeploy, both targets, preview channels included): a FAIL stops the deploy. bun run test drives the board in Chromium, each case a bug a review found there or a promise UI-SPEC.md makes; a board change lands with its case.
Deploys are manual and preview-first — never auto-deploy (see DOSHUS.md).
Session handoffs
Read the newest file in handoff/ before starting work. Every session that changes files
appends one entry to handoff/YYYY-MM-DD.md (today's date; create it if you're first),
dated and stamped on the house clock, MST (America/Phoenix, no daylight saving; OpenClaw pins
it in scripts/lib/localtime.py), never UTC: TZ=America/Phoenix date '+%F %H:%M' gives both.
Claude Code gets that clock by default here from .claude/settings.json. Entries are
chronological and tagged with who did the work — [claude], [codex], [zephyy],
[agy], [gemini], [doshus] — not merely which model ran it:
## [21:40] [claude] Seasonal star palette
- **Changed:** public/js/main.js, public/css/shared.css
- **What/why:** stars engine picks a seasonal oklch palette from the date
- **Verified:** node --check OK; eyeballed all four seasons via a date override on :8080
- **NOT verified:** Safari; prefers-reduced-motion path; never deployed to a preview channel
- **Open:** none
All five fields are required. NOT verified: is the point: name the slices you did not
exercise ("never opened on mobile"), not a hedge ("may need more testing"). none means the
change has no untested surface, not that you ran out of time. A claim of scope in
What/why must be reachable from what Verified: covers. (Forked from the OpenClaw root
AGENTS.md § Session Handoff Log, cut to what this repo needs.)
