veilrender / AGENTS.md
Peng Ding
docs: rewrite AGENTS.md and README for v0.4.0
2424eab
|
Raw History Blame Contribute Delete
6.41 kB

AGENTS.md β€” VeilRender

Context file for AI coding assistants. Symlinked as CLAUDE.md.

What this project is

VeilRender is a headless browser rendering API β€” accepts a URL, renders it with a stealth Chromium browser (CloakBrowser + Patchright), returns HTML/Markdown/readability content. Designed as a fallback for fetch tools that fail on JavaScript-rendered pages.

Supports two deployment modes: single-instance (embedded browser) and gateway + worker pool (browser containers scale independently).

Only runtime dependency is patchright. Everything else is vendored from zerodep β€” HTTP server, S3 client, HTML parsing, readability extraction.

Architecture

Request flow

HTTP request β†’ auth β†’ route handler β†’ browser_manager.get_page()
                                        ↓
                              LocalWorker (embedded CloakBrowser)
                              RemoteWorker (CDP, Chromium)
                              PlaywrightWorker (Playwright protocol, Firefox/Camoufox)

Key components

Component File Purpose
HTTP server _vendor/httpserver Async HTTP framework (zerodep)
Browser pool browser.py LocalWorker, RemoteWorker, PlaywrightWorker, BrowserManager
Config config.py VEILRENDER_* env vars, worker protocol parsing
Routes routes/*.py /render, /screenshot, /health, /metrics, /stats, / dashboard
Storage storage.py L1 TTLCache + L2 S3 (vendored S3Client)
Filters filters.py Outbound request blocklist (StevenBlack/hosts)
CDP proxy cdp_proxy.py WebSocket proxy for direct CDP access
Stats stats.py In-memory counters, p50/p95 latency

Worker types

Type Connection Browser Stealth
LocalWorker Local subprocess CloakBrowser (auto-downloaded) Browser-level + driver-level
RemoteWorker chromium.connect_over_cdp() Any CDP-compatible Chromium Driver-level (Patchright)
PlaywrightWorker firefox.connect() Camoufox or Playwright-served Firefox Browser-level (Camoufox)

Repository layout

src/veilrender/
β”œβ”€β”€ app.py              # HTTP server + CDP WebSocket multiplexing
β”œβ”€β”€ config.py           # Settings from env vars (VEILRENDER_ prefix)
β”œβ”€β”€ auth.py             # Token verification
β”œβ”€β”€ browser.py          # Worker classes + BrowserManager pool coordinator
β”œβ”€β”€ cdp_proxy.py        # CDP WebSocket proxy
β”œβ”€β”€ filters.py          # Outbound request blocklist
β”œβ”€β”€ models.py           # Request/response dataclasses
β”œβ”€β”€ stats.py            # In-memory request counters and latency
β”œβ”€β”€ storage.py          # L1 (TTLCache) + L2 (S3) render cache
β”œβ”€β”€ routes/
β”‚   β”œβ”€β”€ dashboard.py    # GET / β€” stats dashboard with i18n (en/zh)
β”‚   β”œβ”€β”€ health.py       # GET /health
β”‚   β”œβ”€β”€ metrics.py      # GET /metrics β€” Prometheus exposition
β”‚   β”œβ”€β”€ render.py       # POST /render
β”‚   └── screenshot.py   # POST /screenshot
└── _vendor/            # zerodep modules β€” DO NOT EDIT manually
    β”œβ”€β”€ httpserver.py    # Async HTTP server
    β”œβ”€β”€ cache.py         # TTLCache
    β”œβ”€β”€ s3.py            # S3-compatible client
    β”œβ”€β”€ readability.py   # Article extraction
    └── soup.py          # HTML parser
deploy/
β”œβ”€β”€ compose.yaml            # Single-instance (prod)
β”œβ”€β”€ compose-dev.yaml        # Dev instance (port 7861)
β”œβ”€β”€ compose-pool.yaml       # Gateway + CloakBrowser workers
β”œβ”€β”€ compose-pool-mixed.yaml # Gateway + CloakBrowser + Camoufox
└── Dockerfile.camoufox     # Camoufox server image
scripts/
└── download-cloakbrowser.py  # Download CloakBrowser binary

Commands

make dev            # Run dev server on :7860
make build          # Docker build (full target, with CloakBrowser)
make build-gateway  # Docker build (gateway target, no browser)
make run            # Docker run
make lint           # ruff check --fix && ruff format
make typecheck      # ty check
make vendor         # Re-vendor zerodep modules

Deployment

Dev deployment

make deploy-dev SSH_TARGET=oaklight.buttercup          # single-instance
make deploy-dev SSH_TARGET=oaklight.buttercup POOL=1   # gateway + workers

Builds from the working tree (not committed state), tags as dev-test, deploys to veilrender-dev stack (port 7861). Always verify no dirty files in src/ before deploying.

Release

  1. Update CHANGELOG.md β€” move [Unreleased] to [X.Y.Z]
  2. Bump __version__ in src/veilrender/__init__.py
  3. Commit: git commit -m "bump version to X.Y.Z"
  4. Tag and push: git tag vX.Y.Z && git push && git push origin vX.Y.Z
  5. Release workflow auto-publishes to PyPI, Docker Hub, GHCR, and creates GitHub Release from changelog

Production

Prod runs on oaklight.buttercup at /dockervol/dockge/stacks/veilrender (port 7860), exposed via NPS as veilrender.service.oaklight.top.

Update prod to a released version:

ssh oaklight.buttercup 'cd /dockervol/dockge/stacks/veilrender && \
  docker compose pull && docker compose up -d'

Escalation

  • Browser won't start β†’ check _find_browser_binary() cascade: CLOAKBROWSER_BINARY env β†’ ~/.cloakbrowser/*/chrome β†’ auto-download
  • Remote worker won't connect β†’ check _resolve_to_ip() (Chromium rejects non-IP Host headers), verify CDP port is accessible
  • Camoufox worker won't connect β†’ check Playwright version compatibility (client/server major.minor must match)
  • Vendor module issue β†’ never fix in-place; update upstream zerodep, re-vendor
  • Test failure after 3 attempts β†’ stop, report full output
  • Never: delete files to fix errors, skip lint, modify _vendor/ directly

Files to never edit

  • src/veilrender/_vendor/** β€” vendored zerodep modules, update via make vendor or zerodep add from ~/projects/zerodep
  • CLAUDE.md β€” symlink to AGENTS.md, edit AGENTS.md instead

Definition of done

  1. ruff check --fix && ruff format on changed Python files
  2. ty check passes
  3. Server starts and /health returns 200
  4. /render returns valid content for a test URL
  5. /metrics returns valid Prometheus output