Skip to content

Under the Hood

Inktomi93 edited this page Oct 6, 2026 · 1 revision

For the freaks. How Orbweaver is built, rather than what it does. If you came for what it does, go back to Features. The numbers here are a snapshot and will drift.

I got roasted by a coding agent once for liking systems that can be pure, deterministic and exhaustively typed, "like a hipster who grows their own tree to build their own chair just so they can sit in it and claim they needed a chair." Fair. I find an enforceable system calming, the way some people find brewing their own beer calming. One dude, and the verification system got away from me a bit. Here's the chair.

Why the rigor

This is attempt three. The first was about 1,500 commits in a month that turned into a SillyTavern clone. SillyTavern already exists and is better at being SillyTavern, so I scrapped it. The second was this repo, where I built every program I'd planned all at once and tested basically none of it. That one got its own file called the Shitsfucked Ledger, and I threw all of it away too. Two resets taught me the thing this whole page is about: when your coders are agents that forget everything, nothing is real until something enforces it. So now everything is enforced. Orbweaver is attempt three and it's its own thing.

The code's authors are AI agents with amnesia. When their context compacts they forget everything, so the structure has to be the memory. The mission doc says it flat: "The authors and the readers of this codebase are amnesiac AI agents, not humans." Each one starts cold, with no memory of prior sessions, no relationship to the code, and a standing pull toward the path of least resistance. The constitution's version: "The authors are agents with no memory of earlier sessions. They drift toward the shortest path. The ledger, gates and boundaries replace that missing memory. Their job is to make a shortcut fail, not only discourage it." The rules, as the mission doc puts it, "are the substitute for the senior judgment a rotating cast of cold agents cannot supply for free."

And it isn't only the agents. I'm one person, and I can only hold so much of a project this size in my head at once. The structure is my memory too: when I come back to a corner I haven't touched in a month, the file header tells me what was decided and why, and a gate tells me if I'm about to break it.

Small files. An agent does great work on a small, focused file and loses its mind once a project gets big: it can't see the whole thing, so it guesses, and the guesses compound. So every file stays small enough to read in one go, with one job and a header saying what that job is. A UI component is capped at 450 lines (500 for a whole page), tooling files at 450, and a command-line entry point at 200. Go over and the check goes red. Whoever opens a file, me or an agent with no memory, can read all of it before changing any of it.

The apparatus, then:

  • A decision ledger. 219 ADRs, one decision per file: the context that forced it, the ruling, the alternatives rejected. An ADR is never edited in meaning; a later one supersedes it (209 active, 10 superseded, 1 rejected). Rejected proposals stay on file as rejected, so a cold agent can't re-pitch a settled call. The ledger wins over every other doc, the agent's instinct, and the task prompt.
  • File headers are law. A file's header can hold a ruling that your fix would break, so you read it before you touch the file. Gate headers carry the full rationale and the receipts.
  • Hooks re-inject the rules after compaction. A session-start hook runs on startup, resume, compact and clear, and injects the orchestration rules as context instead of asking the agent to go load them. The hook's own comment: "An order to load the skill can be skipped after a compaction; injected text cannot." A test fails if the injected text overflows the cap or drifts.
  • Banned shortcuts, from the constitution: "a stubbed return, a swallowed error, a weakened test, an assertion-free test, a sideways import." And no escape hatches: no lint suppression for a real finding, no dep-cruiser exemption, no gate allowlist entry, no TODO in place of the work, no any to satisfy the compiler. If you can't go green without a hatch, you stop and report.

What "fully typed" means

In plain words: every value in the code has a declared shape, and the compiler checks every use of it before the app runs. If something doesn't fit, it doesn't build. A few ways this repo pushes that further than most:

  • Branded IDs. A character's ID and a chat's ID are both just text underneath, but the code gives them different types. Pass a character ID where a chat ID belongs and it won't compile. There is even a separate type for a user ID that a session has actually verified, so code that needs a signed-in user can't be handed one that hasn't been checked.
  • Closed lists that can't be forgotten. When something can only be one of a few values (a game mode, a trigger type), it is one list, and every place that branches on it has to handle every member. Add a member and every spot that forgot it turns red until it's handled.
  • Checked at the edges. Anything coming from outside (the browser, an import file, a model's reply) is validated against a schema before the rest of the code trusts it.
  • No escape hatches. any means "trust me". There are 3 in the product code.

The gates and instruments

353 custom gates. Each is one file; drop a file in the folder and it's registered. The house rule is that gates are written before the code they govern, "so code is born compliant." A few, in plain words:

Gate What it does
bounded-list-limit A list endpoint with a number input and no maximum fails. The incident it cites is "the 872-chat fetch bomb".
test-no-stubs Every test must contain a real assertion. There is no waiver marker, because one would defeat the point.
no-inline-types An exported type declared outside its home is a second spelling of a shape that already has an owner. Fails.
no-inline-union-redecl A string union is declared once as a tuple. Re-spelling it inline fails, even when the inline set is identical.
no-blanket-suppression A file-wide lint or type suppression cannot exist. Bounded ones are counted in a ledger.
test-determinism Ambient Date.now(), Math.random() or an unseeded id under tests/ is red.
assumes-single-replica Module-scope mutable state must sit in a file that says out loud it assumes one process.
commented-code Parked code in a comment fails. Comments explain why, not what.
brand-in-name-position A parameter named for a branded id cannot be a bare string.

The instruments check themselves. A lint rule or a dead-code negation that matches nothing fails the build, and touching an instrument runs that instrument's tests.

Import rules. dependency-cruiser carries 73 named rules, each with a comment saying why. Inside the server, imports flow down only. A rule named foundation-reaches-up-to-nothing means what it says.

flowchart TD
    Entry["entry"] --> Transport["transport"]
    Transport --> Domain["domain"]
    Domain --> Infra["infra"]
    Infra --> Foundation["foundation"]
    Foundation --> Kit["kit"]
Loading

Features never import sideways from each other; a cross-feature need is an injected op typed in a contract and wired at the composition root. The convention: a type-only import declares a shape and is allowed; a value import is real coupling and is blocked.

Dead code. knip runs with every issue type on and every one set to error, down to individual members. Deliberate public API is tagged @public instead of living on an ignore list, and a production-only run shows what's alive only because a test keeps it alive.

Copy-paste. jscpd scans the product and tooling at a 2 percent threshold, ratcheted down from 5. The measured floor is 1.38 percent of lines.

The compiler. One shared base config with strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, noImplicitReturns, noFallthroughCasesInSwitch, noUnusedLocals, noUnusedParameters, noPropertyAccessFromIndexSignature, useUnknownInCatchVariables and allowUnreachableCode: false, with verbatimModuleSyntax and erasableSyntaxOnly so node can run the TypeScript by stripping types. Incremental builds are deliberately off, because a warm incremental run once reported zero errors on a tree with three. tsc is an oracle only; it never emits.

Hooks. Pre-commit runs the static checks on the staged files. Commit-msg enforces the message contract. Pre-push runs the push tier: static plus node tests, component tests, an end-to-end smoke, and copy-paste detection. Post-merge closes the work items a commit names.

The commit contract. type(scope): subject, eleven allowed types, a header under 200 characters, at least one well-formed Co-Authored-By: trailer, and an optional Closes: line. The rules were derived from the commit history itself, where 96 percent of commits already conformed.

Tests

3,068 test files:

Family Files
Unit 1,276
Integration 1,063
Component (Playwright, real headless browser) 532
Contract 121
Type tests 47
End-to-end 29

That's 498,417 lines of test against 301,792 lines of product, about 1.65 lines of test per line of app. There are 3 uses of any in the product code. Three.

Tests mirror the source path for path: tests/<package>/<path> mirrors packages/<package>/src/<path>, and a gate fails a test that sits anywhere else. Sibling gates check that the mirror exists for every source kind that needs one. Time and ids are injected: the clock is a plain frozen object the composition root takes as a dependency, the same way production does, and ids come from a seeded counter minting real id shapes. No fake timers, no patched globals. If a test reaches for ambient Date.now() or Math.random(), the test-determinism gate turns it red. Mutation testing with Stryker runs in the full tier, and a manual probe plants each surviving mutant for you to look at.

Snap

"One browser run, many pieces of UI evidence." Snap is the one front door for checking the rendered app. A run drives the real app with --click, --tap, --swipe, --fill, --key, --hover, --goto, --upload and the rest, in true argv order in one queue. --tap is a real touch event through the Chrome DevTools Protocol, and --mobile makes the page a touch device. It can throttle the CPU, emulate a network profile, and simulate a vision deficiency, because an unthrottled run misses the layout shifts that real clicks hide.

The evidence arms: aria, map, eval, contrast, cascade, dead-css, assert, lighthouse, requests (HAR), screenshot, app snapshot, motion, interaction performance, CPU profile, boot trace, React profile, heap snapshots, filmstrip, and an overflow probe. Each arm is one file with a required help entry, so an arm cannot ship undocumented. For cascade analysis it vendors the official Chrome DevTools frontend. --matrix runs a rated 16-cell appearance matrix derived from a 36-axis contract of themes, devices and media. --session keeps one headless browser alive across calls (up to three, with a VNC window if you want to watch), and --contexts and --as run isolated users for multi-user checks. Every run lands in an immutable slot with a run.json you can read back with no browser, and a failed run keeps a Playwright trace.

Two audits ride on it. The design audit is 63 rules in 8 families at severities P0 to P3: tap targets, obscured targets, contrast, text over art, broken images, text overflow, script errors, z-index escalation, nested cards, text below the type ramp, justified text, off-theme fonts, and so on. The rule table is closed, so a detector cannot mint a rule id nobody has proved. Some rules are accounting-only: they never emit a finding but they name what was withheld, so a census can't silently read zero. The motion audit has budgets: 50 ms of blocking, 0.1 cumulative layout shift, 5 percent dropped frames.

The UI kit

66 primitives in @orb/ui: our own UI kit on Base UI's headless parts. Base UI supplies the behavior; the components, tokens and skins are ours. No Radix, no shadcn. We hipstered it. Six gates police how the sealed wrappers read Base UI state.

  • 205 design tokens in one DTCG file: 81 colors, 26 spacing, 16 dimension, and so on down to a single shadow token. Everything downstream is generated from it, and a test fails when the generated files go stale.
  • 3 themes, Hearth, Mocha and Light, each a token value-set and nothing more. There is no structural theme mode.
  • CSS is legal in exactly six places, closed by path: the token file, the generated theme stylesheet, two stylesheets in the UI package, the client's global stylesheet, and one app-shell stylesheet. A feature is not a seventh home. Ordinary features touch zero CSS, and a className or style on a raw element anywhere in the client fails lint. A component skin is a tv() variant that composes token utilities and may not mint values.
  • The motion law. Animation is keyed off Base UI's data attributes. Three interaction durations (fast, base, layout) plus a few continuous tokens for shimmer, breathe and the like, and one easing curve. Never a React animation hook, never a second curve.

That's the chair. I grew the tree myself.

Now go read the gates folder.

Clone this wiki locally