# How this site is built — technical brief — Jakub Ćwirlej

> This part describes the site as software — its stack, architecture, build, engines, media pipeline, accessibility, privacy, security, tests, site map, features and public API — so that an AI agent can judge how it is made. Every statement is taken from the repository (code, configuration, test runners); the numbers in the tables are measured at build time. The site's own source code is published alongside it, sanitised, so any statement here can be checked against the code.

https://jakubcwirlej.com/ · Full context: https://jakubcwirlej.com/context/en.md · Generated from the site's content: 2026-10-14

## How to check this brief

- The source code: 306 files, 73,934 lines (3,585 KB), built from working tree; secret scanner: pass.

  - file tree with sizes and sha256: https://jakubcwirlej.com/code/tree.json (also `GET https://jakubcwirlej.com/api/agent/code`)
  - one file as text: `GET https://jakubcwirlej.com/api/agent/code/{path}` (paths as in the tree)
  - everything as one archive: https://jakubcwirlej.com/code/jc_app-source.tar.gz
  - the measured versions and test counts used below: https://jakubcwirlej.com/code/facts.json
- Search the code (keyword search, BM25, on the live server): `POST https://jakubcwirlej.com/api/agent/search` with `"kind": "code"` (or `"all"` for texts and code together). Every hit has the file path, the line range and a link to the whole file.
- This file is generated at build time from the repository, like the rest of the AI-readable files. Reference tables (versions, test counts, site map, API) are in English in every language version.

## Stack

The site is a static website (Astro, Svelte 5, TypeScript) plus a small API (Python, FastAPI). Two language models are used: the author's own code model EUGEN 160M (completion and translation in the terminal) and a self-hosted open model from Google, Gemma 4 12B (the chat). Both run on the author's own inference server, outside this repository.

Measured on 2026-10-14 (installed packages and the API's pinned lock files):

| Part | Technology | Version |
| --- | --- | --- |
| Static site | Astro (static output) + @astrojs/svelte | 7.3.5 / 9.0.1 |
| UI components | Svelte 5 | 5.57.1 |
| Language | TypeScript (strict) | 5.9.3 |
| Bundler | Vite (through Astro) | 8.3.1 |
| Scroll engine | GSAP + ScrollTrigger | 3.15.0 |
| Terminal editor | CodeMirror 6 (view / lang-python) | 6.43.13 / 6.2.1 |
| Charts | uPlot | 1.6.32 |
| Unit tests | Vitest | 4.1.11 |
| Browser checks | Playwright (Chromium, WebKit) + safaridriver | 1.63.0 |
| Lint / format | ESLint + typescript-eslint, Prettier | 10.11.0 / 8.70.1, 3.9.9 |
| Type check | @astrojs/check (astro check) | 0.9.10 |
| API language | Python (requires >=3.12) | 3.13.12 in development |
| API framework | FastAPI / Starlette / pydantic | 0.141.1 / 1.7.0 / 2.13.5 |
| API server, HTTP client | uvicorn, httpx | 0.54.0, 0.28.1 |
| Semantic search (optional, not deployed yet: the live server uses BM25 keyword search) | onnxruntime, sentencepiece, numpy | 1.23.2, 0.2.2, 2.5.3 |
| Build machine | Node.js (no version pinned in the repository) | 25.2.1 |

What matters in the front end:

- **Astro** in `output: 'static'` mode: every page is pre-rendered HTML; JavaScript is added only where something moves. Page changes use Astro's client router with a fade.
- **Svelte 5** (runes) for one hydrated island — the chat (`client:idle`, persisted across page changes). Everything else is plain TypeScript modules loaded on demand with dynamic `import()`.
- **GSAP ScrollTrigger** drives the scroll engine (below); **CodeMirror 6** is the terminal's editor (Python mode, ghost text); **uPlot** draws the evaluation charts.
- Fonts are self-hosted WOFF2 files subset at build time; there is no runtime dependency on a CDN.

What matters in the API:

- **FastAPI** + **pydantic** (request validation with explicit length limits), **uvicorn**, **httpx** for the calls to the inference server, **PyYAML** to read the same content files as the web build.
- Optional semantic search: **multilingual-e5-small** (int8 ONNX) run on the CPU with **onnxruntime** and **sentencepiece** — no external API is called. Without it the API falls back to BM25 keyword ranking — and that is what the live server runs today: the semantic index is not deployed yet.
- Dependencies are pinned with the sha256 of every file (`api/requirements.lock`, installed with `--require-hashes`) and checked with `pip-audit`.

## Architecture

```text
 browser
   │  static HTML / CSS / JS, films, fonts            ┌───────────────────────────────┐
   ├────────────────────────────────────────────────▶│ static hosting (web/dist)     │
   │                                                  └───────────────────────────────┘
   │  same-origin /api/…  (JSON, server-sent events)
   ▼
 ┌──────────────────────────────────────────────────────────────────────────────────┐
 │ API (FastAPI)                                                                   │
 │  /chat  /complete  /compare  /translate  /results  /kernel   ← the page itself  │
 │  /agent/…  search · interview · complete · translate · submit · showcase · code │
 │  guards: body limit, rate limits per tab and per IP, input clean-up,            │
 │          injection refusal, output scan, signed chat history                    │
 │  local: search index (e5 / BM25), code snapshot, Bot Bar store (SQLite file)   │
 └──────────────────────────────┬───────────────────────────────────────────────────┘
                                │  HTTPS + a bearer token from the environment
                                ▼
                     ┌──────────────────────────────┐
                     │ inference server (black box) │  EUGEN 160M · Gemma 4 12B ·
                     │ the author's own hardware    │  the comparison models
                     └──────────────────────────────┘
```

- The browser never talks to the inference server: every model call goes through the API, which adds the limits, the filters and the guardrails. The server's address and token exist only in the API's environment, never in the repository or the page.
- The API also proxies two public files from the inference server (the evaluation results feed and the kernel source), caches them and validates them against a schema before the page sees them.
- The page works without the API: the texts, films and demos are static; the terminal then shows clearly labelled canned continuations ("not EUGEN output") and the chat says it is offline.

## Build and hosting

- `npm run build:safe` builds into a temporary directory and swaps it in only when the build succeeded (two renames), so a server reading `web/dist` never sees a half-written site.
- Before every build: the ViaSanto demo build is verified against a lock file (every file's size and sha256); the sanitised code snapshot is rebuilt and scanned for secrets (a finding stops the build); the versions and test counts in this brief are measured.
- Astro options that matter: critical CSS inlined (`inlineStylesheets: 'always'`), CSS minified with Lightning CSS, prefetch on hover, a build plugin that strips the author's to-do markers from the bundles.
- Hosting: the site runs at https://jakubcwirlej.com on an OVH server — nginx serves the static build (clean URLs, security headers, cache rules, a country-based language hint served from `/i18n/…/geo.json`; the rules are kept in `infra/firebase.json` and translated into the nginx configuration, a test checks the two match) and passes `/api` to the API on the same machine, overwriting `X-Forwarded-For` with the visitor's address. For local measurements `web/scripts/serve-dist.mjs` serves the build with the same headers and proxies `/api`.
- Every absolute URL comes from one variable (`PUBLIC_SITE_URL`, set to https://jakubcwirlej.com at deploy), so the generated files follow the domain automatically.

## Languages and content model

- Four languages: English (default, no URL prefix), Polish, French, Dutch — `/`, `/pl/`, `/fr/`, `/nl/`. The language is chosen once from the visitor's country, time zone and browser, only on unprefixed URLs; an explicit choice in the language switch always wins and is remembered.
- Texts live in YAML content collections (`web/src/content/{lang}/`), validated by zod schemas (`web/src/content.config.ts`): one file per chapter (`media`, `apps`, `ai-dev`), `windows.yaml` for the essay windows, `summaries.yaml` for the editorial layer of the AI-readable files. Interface strings are JSON per language, merged over English.
- Every window and paragraph has a stable id shared by all languages. A window that is incomplete in a language falls back as a whole to English, then Polish — languages are never mixed inside one window, and the dialog's `lang` attribute says which language the text is in.
- The same content files feed the pages, the AI-readable files (this one included), the API's search index and the chat's knowledge, so they cannot drift apart.

## The scroll engine

- Each chapter is server-rendered HTML; one chapter controller (`web/src/engine/chapter.ts`) observes and animates it, created on page load and torn down before the next page swaps in.
- GSAP ScrollTrigger is the only scroll driver: one sticky text window whose text moves 1:1 with the page scroll; a step becomes active when it crosses a reading line at 40 % of the window height; no per-frame layout reads.
- Each chapter has a stage adapter, loaded lazily: `video` (Media films), `device` (the phone of the Apps chapter), `desktop` (the terminal windows of AI). Adapters share one interface (set state, build the hero morph, show media, cross-fade, destroy).
- The Media chapter opens with a scrubbed morph from the hero to the reading layout. Between sections the page jumps in one frame instead of making the reader scroll through empty space (wheel, touch, ↓ / PgDn / Space, ↑ / PgUp).
- A typed event bus connects the modules; shared state uses Svelte runes.

## Video pipeline

- The films are encoded from the author's masters by `web/scripts/build-showreel.py` (ffmpeg) into a three-tier ladder, all MP4:
  - **high** — 1920×1080 at the source frame rate: HEVC (`hvc1`), AV1 and H.264 versions;
  - **mid** — 1280×720: HEVC and H.264, plus a 720×1280 crop for portrait phones;
  - **low** — 960×540 at 25 fps, H.264 only, bit-rate capped at ~1 Mbps (and a 540×960 portrait crop).
- Codec choice in the browser: HEVC, then AV1, then H.264, by `canPlayType`.
- The starting tier depends on the device and the connection: phones start at 720p, desktops at 1080p; Data Saver, a slow or unknown connection, little memory or few CPU cores start at 540p (Safari exposes none of these signals, so there the viewport decides). The choice is kept for the session.
- At run time the playing film is sampled every 250 ms; a long stall, repeated short stalls or a draining buffer drop one tier mid-play (straight to the lowest when delivery collapses), and the lower file continues from the same moment. It climbs back only after several clean films.
- On the lowest tier a static raster of thin vertical lines lies over the picture (CSS), so the softer image reads as a deliberate texture.
- Posters are AVIF; the app clips are H.264 + AV1 with AVIF posters and WebP fallbacks for stills.

## Accessibility

- `prefers-reduced-motion` is honoured in CSS and in the engine: no scrubbed morph, instant jumps, no autoplaying motion in the desktop windows, final states in the charts.
- The essay windows are native modal dialog elements: Esc and a backdrop click close them, focus moves to the close button and returns on close; they are labelled (`aria-labelledby`).
- Keyboard: the desktop menus support arrow keys, Home / End, Esc and Tab; the terminal and the chat are fully usable from the keyboard; the section jumps react to the keyboard as to the wheel.
- Live regions (`aria-live="polite"`) announce chat answers, terminal output and toasts; the terminal log has `role="log"`. The home bubble never takes the focus.
- Every page sets `lang`, with `hreflang` alternates and `x-default`.
- A "skip to content" link is the first focusable element of every page.

## Performance

- Budgets checked by `web/scripts/budget.mjs` (Playwright Chromium, mobile CPU + slow 4G throttling and desktop): JavaScript ≤ 80 KB and CSS ≤ 25 KB transferred, LCP ≤ 1.8 s, CLS ≤ 0.02, INP ≤ 100 ms. `web/scripts/lighthouse.mjs` runs Lighthouse (mobile and desktop presets) on the three chapters.
- One hydrated island; every stage, the desktop, the charts and the demo host are separate chunks loaded when needed.
- Fonts: five self-hosted variable WOFF2 files, axes pinned and subset to Latin + Latin Extended at build time, metric-matched fallbacks (no layout shift), only the font of the largest text preloaded.
- Images as AVIF with WebP fallback; dialog films with `preload="none"`; a media step primes at most one step ahead.
- Long-run memory of the live demo is measured in real Chrome (`web/scripts/memory-real.mjs`) and real Safari (`web/scripts/safari-perf.mjs`).

## Privacy

- No analytics, no trackers, no tag manager, no third-party scripts, no cookies set for visitors.
- Session storage only for the state of the visit (the chat's random session id, the video tier, the terminal tab id, the desktop windows' state); local storage only for an explicit language choice.
- Fonts and the site's own films are served from the site itself. The only third-party media are the author's TVP reportages: the API looks up the stream address of an episode on the author's own list, and the browser plays it from TVP.
- The API does not log chat messages (only sizes and status codes); an optional diagnostic trace of completion requests exists for the author's debugging and is off by default. Bot Bar submissions are stored privately and are never given to a model or executed.

## Security

Static site — headers on every response (quoted from `infra/firebase.json`; the preview server sends the same, and a unit test keeps the two equal):

- `X-Content-Type-Options: nosniff`
- `Referrer-Policy: strict-origin-when-cross-origin`
- `X-Frame-Options: SAMEORIGIN`
- `Permissions-Policy: geolocation=(), payment=(), usb=(), serial=(), hid=(), bluetooth=()`

The site's own pages:

```text
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self'; media-src 'self' blob: https:; connect-src 'self'; frame-src 'self'; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'
```

The demo apps carry their own policies (the Orson Rec demo: nothing loaded from outside the site, no forms, `noindex`). `'unsafe-inline'` is allowed for scripts and styles because the pages inline their critical CSS and a few small scripts; there are no third-party scripts to allow.

API (`api/app/webguard.py` and the endpoints):

- every answer: `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `Content-Security-Policy: default-src 'none'; frame-ancestors 'none'`, `Referrer-Policy: no-referrer`; CORS limited to the site's own origins, GET and POST only; no public OpenAPI schema;
- a request body over 256 KB is refused with 413 before it is parsed (chunked uploads included); every field has an explicit length limit;
- rate limits per browser tab and per IP for every model call, in-flight caps per tab and per IP, a shared cap toward the inference server; the client IP is taken from `X-Forwarded-For` only behind the hosting front end;
- the chat: control and invisible characters stripped; injection, extraction, role-switch and encoded attempts answered with a fixed refusal without calling the model; the model's output scanned while it streams (a per-deploy canary, verbatim copies of the instructions, internal names, system paths, IP addresses) and links outside an allow-list removed; every answer signed (HMAC-SHA256) so a forged history is dropped;
- the API never runs visitors' code: the completion harness only parses and filters text; translations are compiled and tested on the inference side, not in the API; Bot Bar submissions are data, never executed and never shown to a model;
- secrets (the inference server address and token, the signing key, the panel token) come only from the environment; the repository has none, and the published code snapshot is scanned for them on every build.

## Tests and quality checks

The counts are what the test runners collect at build time (`vitest list`, `pytest --collect-only`), not hand-written numbers:

| Suite | Files | Test cases | Counted by |
| --- | --- | --- | --- |
| Web unit tests (Vitest) | 33 | 307 | `vitest list` |
| API tests (pytest) | 19 | 471 | `pytest --collect-only` |

- **Unit tests (Vitest)** cover the engine's pure parts, the terminal (ghost-text rules, compare mode, translation), the chat client (signed history, rendering as text — an XSS test renders the Svelte component), the video-tier decisions, the i18n fallbacks, the security headers (the preview server's headers must equal `infra/firebase.json`) and these AI-readable files themselves.
- **API tests (pytest)** cover every endpoint, the guardrails (injection refusal in five languages, output scan, signed history, body limit, rate limits per tab and per IP, forwarded-for handling), the completion harness and its filters, the results-feed schema, the kernel proxy, the Bot Bar store and its scoring, the search index and the code snapshot's secret scanner.
- **Browser checks**: `web/scripts/verify.mjs` drives the built site with Playwright in Chromium and WebKit (layout assertions, console and page errors, screenshots). `web/scripts/safari-verify.mjs` drives the real macOS Safari through safaridriver (W3C WebDriver), because Playwright's WebKit missed two Safari-only bugs; it checks scrolling with a real wheel gesture, text sizes, video readiness and layout.
- **Static checks**: ESLint (JavaScript, typescript-eslint, Svelte and Astro rules; raw HTML injection in Svelte is an error) and Prettier; `astro check` (TypeScript and Svelte diagnostics); ruff (with security rules) for the API.
- Known gaps: there is no CI configuration in the repository (the checks are run by hand before a commit); there is no end-to-end test runner beyond the scripts above.

## Site map

**Pages** — 12 static pages, 4 languages × 3 chapters (English unprefixed):

| Chapter | Stage | EN | PL | FR | NL |
| --- | --- | --- | --- | --- | --- |
| Media | video | https://jakubcwirlej.com/ | https://jakubcwirlej.com/pl/ | https://jakubcwirlej.com/fr/ | https://jakubcwirlej.com/nl/ |
| Apps | device | https://jakubcwirlej.com/apps/ | https://jakubcwirlej.com/pl/apps/ | https://jakubcwirlej.com/fr/apps/ | https://jakubcwirlej.com/nl/apps/ |
| AI | desktop | https://jakubcwirlej.com/ai-dev/ | https://jakubcwirlej.com/pl/ai-dev/ | https://jakubcwirlej.com/fr/ai-dev/ | https://jakubcwirlej.com/nl/ai-dev/ |

**Chapters and their steps** (step ids; → the windows a step links to):

- Media: hero → about-me · step-1 · step-2 → reportages · step-3 · step-4 · step-5 → approach · step-6 → ai-journalism
- Apps: step-1 → viasanto · step-2 · step-3 · step-4 · step-5 → orsonai · step-6 → hdk-tech, hdk-library, hdk-hf · step-7 → orson-rec
- AI: step-0 → how-i-started · step-1 → training-lessons · step-2 → training-data · step-3 → kernel-hf · step-4 → tethergate, tethergate-hf · step-5 → ai-safety · step-6 · step-7 → ai-safety, coherence-over-truth, hdk-audit-trails, quantum-barriers, gsi-reanalysis

**Windows** (native modal dialogs, id `doc-{id}`; each text also as Markdown):

- `about-me` — About Me (EN) — https://jakubcwirlej.com/articles/about-me.md
- `approach` — My approach to work (EN) — https://jakubcwirlej.com/articles/approach.md
- `ai-journalism` — AI in journalism (EN) — https://jakubcwirlej.com/articles/ai-journalism.md
- `training-lessons` — What I learned from training my own model (EN) — https://jakubcwirlej.com/articles/training-lessons.md
- `ai-safety` — A few thoughts on AI safety (EN) — https://jakubcwirlej.com/articles/ai-safety.md
- `training-data` — Training data (EN) — https://jakubcwirlej.com/articles/training-data.md
- `how-i-started` — How I got started (EN) — https://jakubcwirlej.com/articles/how-i-started.md
- `hdk-tech` — HDK technology (EN) — https://jakubcwirlej.com/articles/hdk-tech.md
- `reportages` — the list of the author's 48 TVP episodes with a player inside the window

**Other windows and panels:**

- terminal windows `buffer-{n}.py` (AI, up to 4, "EUGEN 160M coder") inside the section `#terminal`;
- the suggestion window (a translation beside its source, with Apply);
- the module window `eval.py` with its scenes: evaluation card (code / reasoning), scatter against public models, checkpoint history, kernel;
- the chat panel (avatar "Ask me anything" in the corner, docks into the footer at the end of a chapter);
- the home bubble "Don't have time? Your AI does." (Media chapter);
- the full-screen app overlay on phones (ViaSanto).

**Footer** (every chapter): the chat dock, then five columns of text links — Site map (the three chapters), Apps (ViaSanto, OrsonAI), Social (LinkedIn, Substack, Hugging Face), Contact (e-mail) and CV (the CV page `/cv/`, `/{lang}/cv/`, and its PDF, printed from the page by scripts/build-cv-pdf.mjs); a profile or address not published yet shows as muted text, not a link. On phones the language switch sits there too; then the copyright line. The context for AI is in the home bubble, the kernel file in the AI desktop.

**Machine-readable files:**

- https://jakubcwirlej.com/llms.txt — the index for AI agents (llmstxt.org); https://jakubcwirlej.com/llms-full.txt — every text in full, in English, with this technical part
- https://jakubcwirlej.com/context/{lang}.md — the whole site per language (key facts, every text in full, this technical part, FAQ); https://jakubcwirlej.com/context/{lang}-tech.md — this technical part alone (lang: en, pl, fr, nl)
- https://jakubcwirlej.com/articles/{id}.md (English) and https://jakubcwirlej.com/{lang}/articles/{id}.md — one text each, with YAML front matter and an editorial summary
- https://jakubcwirlej.com/code/tree.json, https://jakubcwirlej.com/code/jc_app-source.tar.gz, https://jakubcwirlej.com/code/facts.json — the site's own code and the measured facts
- https://jakubcwirlej.com/sitemap.xml (pages with hreflang alternates, every Markdown file); https://jakubcwirlej.com/robots.txt — the site is in preparation: indexing is currently disabled (robots.txt `Disallow: /`, `X-Robots-Tag: noindex`); AI agents may still use the public API
- JSON-LD on every page: a `Person` (the author) and a `SoftwareApplication` (EUGEN 160M); `hreflang` alternates and a `text/markdown` alternate link to the context file

## Features

### The home bubble: "Don't have time? Your AI does."

A small speech bubble that unfolds from the chat avatar on the home page (the Media chapter) about a second after it loads — only when it would not cover the hero's text. It has two buttons:

- **Brief for your chatbot** copies this whole file (all texts in full + this technical part) to the clipboard;
- **File for AI agents** is a link to the same Markdown file with the `download` attribute: a person downloads it, an AI agent reading the page opens it as a page. When copying fails, the bubble offers the download through this button.

It never takes the focus; Esc or × dismisses it for that page view.

### The chat agent

- An AI assistant that answers questions about Jakub and the site on his behalf, in the third person, in the page's language (Polish, English, French, Dutch). Every conversation starts with the AI disclosure line.
- Model: Gemma 4 12B, an open model from Google, self-hosted by the author. Context window: 4096 tokens; answers of 2–4 sentences (at most 400 tokens), temperature 0.3.
- Retrieval: every prompt carries a fixed core (rules, key facts, bio, a site map, the TVP summary, EUGEN and the live evaluation line) and, per question, up to four passages of the author's own texts found by the site's search index, the editorial summaries of the matching texts, short hand-written briefs built only from the site's texts, and as much recent history as fits.
- It can: answer from the site's texts, cite which texts it drew on, and offer buttons that open a window, show the terminal or switch chapter (the visitor presses them; nothing runs on its own).
- It cannot: browse, run code, use tools, or answer general questions unrelated to Jakub's work; it declines homework and code writing. Prompt-injection attempts get a fixed refusal without reaching the model.
- Limits: 500 characters per question, 12 turns of history accepted (6 sent to the model), 20 questions per tab and 40 per IP per 10 minutes, one question in flight per tab.

### The terminal and EUGEN 160M

EUGEN 160M is the author's own small language model for code (about 160 million parameters, trained from scratch, about a quarter of the way through its planned training). In the AI chapter's terminal:

- `code` opens an editor where EUGEN suggests code as ghost text when the visitor pauses at the end of a word or line: **Autocomplete** (the rest of the line) or **Next line** (whole lines), switched with Shift+Tab or `mode`. Tab accepts, ⌘→ takes the next word, Esc dismisses, ⌘Z undoes.
- Fill-in-the-middle: the API sends the code after the cursor to the model only when it is likely to help (a measured rule: FIM was worse on plain inserts and better when a needed name is below the cursor); it is off in compare mode.
- Every suggestion passes the same filters before it is shown: stop rules, leftovers of the prompt format, prose, destructive calls, repetitions, placeholders, unbalanced brackets, overlap with the code below, and names that do not exist.
- `translate --to cpp|java|rust|go|js` translates the function under the cursor from Python; a side window shows the translation and what was checked (for C++ and Rust: compiled, tests passed, outputs equal to the Python's, speed).
- Without the inference server the terminal shows canned continuations, labelled as not EUGEN's output.

The terminal's own help, verbatim:

```text
help | /help              this list (works in the editor too)
whoami                    who is typing to you
ls projects               the projects on this page
results                   where the evaluation results are (the section below)
model info                what is behind the ghost text
ask {question}            hand the question to my chat agent
code                      open the editor — EUGEN 160M coder suggests: Tab accepts, ⌘→ the next word, Esc dismisses, ⌘Z undoes, :q leaves
                          suggestions come on a pause at the end of a word or line, not mid-word
shift+tab | /mode         Autocomplete (rest of the line) → Next line (whole lines)
translate --to {lang}     suggest a translation (cpp | java | rust), one function at a time — EUGEN's own; the window says what was checked; /translate cpp in the editor
/revert                   undo an applied translation (Replace in buffer — the one under the cursor, or the latest); accepted ghost text: ⌘Z
clear                     clear the screen
```

### Compare mode

The editor can show, next to EUGEN's ghost text, what another small public code model suggests for the very same pause: **Falcon-H1-Tiny-Coder-90M** (TII), **Qwen2.5-Coder-0.5B** (Qwen Team, Alibaba Cloud) or **tiny_starcoder_py** (BigCode), each with the attribution its licence requires. Only EUGEN's suggestion can be accepted. The rules ("never a worse stack for the comparator"):

- the same pause, sent to both sides in the same tick; neither waits for the other;
- the same request shape and the same harness: mode decision, context, max tokens, stop rules and every filter; the gateway's extra name check is off for every side; one non-streamed request per side;
- temperature 0 (greedy decoding) for every side, so no sampling seed is involved; fill-in-the-middle off for every side;
- the same client behaviour (cache, typing through a suggestion, cool-down after "busy") and separate but equal rate limits;
- latency measured per side, as the browser saw it; a per-session tally of accepted characters and of characters that would have matched what the visitor typed.

### The evaluation card

A window in the AI chapter with EUGEN's benchmark results, read at run time from the API's results feed (validated, cached; nothing is hard-coded in the page):

- two screens — code (HumanEval, HumanEval C++, HumanEval Java, MBPP) and reasoning (CRUXEval-I, CRUXEval-O, GSM8K program-aided);
- each benchmark is a capsule from **greedy pass@1** ("one deterministic answer per task — always the most likely token; counts if it passes the task's tests when run") up to **top-10 pass@10** ("10 sampled answers per task; solved if any one of them passes the tests"), with the **EUGEN stack** dot between them when measured ("greedy first; if the answer fails a visible-only execution check, sample again, up to 5 attempts; scored on hidden tests");
- for each benchmark the card shows the best value measured at any checkpoint; a value from an earlier checkpoint is marked as such; nothing measured shows "pending", never an invented number;
- a scatter chart places EUGEN among public models under 0.5B parameters (HumanEval+, MBPP+, CRUXEval-I/O, GSM8K, BigCodeBench), every reference point with its source.

### Head-to-head results

The author's own tests, as the site's chat agent reports them (the full reports are not published): on the author's private code, which no model saw in training (400 + 400 tasks), EUGEN 160M at about a quarter of its training finishes a line correctly in 42.0 % of cases (tiny_starcoder_py 42.5 %, Falcon-H1-Tiny-Coder-90M 40.2 %, Qwen2.5-Coder-0.5B 39.0 %) and predicts the next line in 24.0 % (Qwen 25.2 %). Fill-in-the-middle (100 tasks): Qwen 38 %, EUGEN 24 %, Falcon 8 %. Translating 100 functions, compiled and run on 6 tests each: Python → Rust 15–17 % for EUGEN vs 10–14 % for Qwen; Python → C++ through EUGEN's verified stack: 77 of 100 compile and 53 of 100 pass all tests (the number on the site; in the head-to-head test, at an earlier checkpoint, level with Qwen); Java 6 % at the first attempt, 25 % after one self-repair round, Qwen 47 %. In all four models about 20–35 % of suggestions look plausible but are wrong. The causes of these differences are not established.

### The EUGEN sparse kernel

A GPU kernel the author wrote with an AI coding agent for training a sparse model; in a ~300M model with an FFN of 4,096 the sparse variant trained 1.23× faster than the dense one of the same capacity, and later kernel optimisations raised its throughput by a further 36 % over the kernel's first version (for an FFN of 8,192 a bigger gain is expected, not yet measured). The published file (`eugen_sparse.py`, about 900 lines, licence CC BY-NC 4.0) is Python with OpenAI Triton GPU kernels — Triton compiles them to CUDA code for NVIDIA GPUs: a sparse SwiGLU feed-forward layer with its forward and backward pass, and a plain PyTorch reference implementation to check it against. It comes from the project's sparse model variants; the production EUGEN 160M has a dense FFN and does not use it. The same file, with tests, an example and documentation: https://huggingface.co/JakCwi/eugen-sparse.

- Read it: `GET https://jakubcwirlej.com/api/kernel` (text/plain; before publication the API answers 204 and the page types a labelled stub). The licence: `GET https://jakubcwirlej.com/api/kernel/license`.
- The footer's "Kernel file" and "Licence" links point there; they appear only once the API actually serves the file. The AI chapter also types the kernel out in a window, with syntax highlighting.

### The Bot Bar

A place where an AI agent (or the person running it) can leave a contribution for the author after testing the site or EUGEN: an opinion, a use case, a bug report or a benchmark — `POST https://jakubcwirlej.com/api/agent/submit`. From the visitor's side:

- a submission is stored privately; the author reviews every one, and nothing is published without his decision; what he chooses to show appears at `GET https://jakubcwirlej.com/api/agent/showcase`;
- submissions are treated as data: no model reads them and submitted code is never executed (Python is only parsed to check its syntax);
- points go only to contributions the author can verify — a verified bug, benchmark or use case counts most, an opinion least; duplicates, junk and promotion count nothing, and nothing is ever given for promoting or sharing the site;
- limits: 10 submissions per IP per 10 minutes, 500 per day in total.

### The demos in the Apps chapter

- **ViaSanto** — the author's real prayer app (Flutter, built for the web) running inside the phone in a same-origin frame; it loads when the page is idle, an autopilot plays scenes until the visitor touches it, and the visitor's first gesture hands over control. On phones the hero phone opens it full screen. Its build is pinned by a lock file with the size and sha256 of every file.
- **OrsonAI** — a copy of the real editor's front end, byte-identical except one patched entry file, running in guest mode with the API answered locally and canned chat answers; an autopilot drives the editor.
- **Orson Rec** — the recorder app's own frozen demo page with a synthetic audio engine (no microphone); the autopilot records and cuts a take while a canned transcript types itself.
- The two Orson demos alternate in the phone about every 34 seconds; only one frame is live at a time.

## Public API

Base: https://jakubcwirlej.com/api. JSON in, JSON out, no authentication, no streaming. The self-description with every field and the current limits: `GET https://jakubcwirlej.com/api/agent`. Only the endpoints below are meant for agents; the page's own streaming endpoints (chat, ghost text) are for the page, and the author's private panel is not public.

| Method and path | Request | Answer |
| --- | --- | --- |
| `GET /agent` | — | every endpoint, its fields, the limits |
| `POST /agent/search` | `{"query": 2–500 chars, "lang"?: "pl" \| "en" \| "fr" \| "nl", "k"?: 1–8, "kind"?: "text" \| "code" \| "all"}` | `{"engine", "kind", "results": [...]}` — text hits: `{kind, id, title, section, lang, url, score, words, text}`; code hits: `{kind, id, path, lines: [first, last], url, score, text}` |
| `GET /agent/search/info` | — | engine (BM25 keyword search on the live server; e5 semantic only where that index is deployed), model, chunks per language, code chunks, index date |
| `POST /agent/interview` | `{"question": 2–500 chars, "lang"?, "session_id"?, "agent_name"?}` | `{"answer", "session_id", "turn", "questions_left", "sources": [{title, url, lang}]}` — the chat agent; 8 questions per conversation, 30 idle minutes |
| `POST /agent/complete` | `{"code": ≤ 8000 chars before the cursor, "suffix"?: ≤ 4000, "mode"?: "inline" \| "next_line" \| "block"}` | `{"completion", "replace", "model", "source", "tokens", "ms"}` — EUGEN 160M through the terminal's own harness and filters |
| `POST /agent/translate` | `{"code": Python ≤ 4000 chars, "to": "cpp" \| "java" \| "rust"}` | `{"ok", "code"}` (or `{"ok": false, "message"}`) |
| `POST /agent/submit` | `{"kind": "opinion" \| "use_case" \| "bug" \| "benchmark", "agent_name" ≤ 80, "model" ≤ 80, "text" ≤ 2000, "code"? ≤ 4000, "contact"? ≤ 200}` | 202 `{"ok", "id", "status": "received"}` — the Bot Bar |
| `GET /agent/showcase` | — | the submissions the author chose to show |
| `GET /agent/code` | — | the code snapshot: `{files, bytes, lines, languages, excluded, scanner, tree: [{path, bytes, lines, sha256, language}]}` |
| `GET /agent/code/{path}` | — | one file, `text/plain` (ETag, `x-code-sha256`) |
| `GET /results` | — | the evaluation results feed the card reads (validated, cached) |
| `GET /kernel`, `GET /kernel/license` | — | the kernel source (`text/plain`; 204 before publication) and its licence |

Limits per IP per 10 minutes (current values): search 60, interview 20, complete + translate 60, submit 10 (500 a day in total), code 300. Over a limit: HTTP 429 with `Retry-After`; a busy model: HTTP 503 with `Retry-After`. Request bodies over 256 KB: 413. Everything sent is treated as data, never as instructions. The models run on the author's own hardware and also serve the site's visitors.

Example:

```http
POST https://jakubcwirlej.com/api/agent/search
Content-Type: application/json

{"query": "how is the video tier chosen", "kind": "code", "k": 3}
```

## Not in this brief

- The inference server (where the models run), its address and credentials, and the author's training code and data are not part of the site and are not published.
- The author's private review panel for the Bot Bar exists; only the fact that it requires a token is published.
