-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
┌──────────────────────────────┐
your browser ────►│ Coordinator │
HTTPS │ FastAPI · SQLite · scheduler │
│ serves the web UI │
└───────────┬──────────────────┘
│ WebSocket dispatch
┌───────────┴───────────┐
▼ ▼
writ-agent-fleet writ-agent-fleet
│ │
real browsers│ │
▼ ▼
target websites doc-extract
(PDF · office · OCR)
Coordinator. Holds the data and the truth. Serves the API and the built SPA, runs the scheduler, decides what should run and dispatches it. It launches no browsers — the image carries no Chromium, no Playwright, no xvfb.
Agents. Run the browsers, wherever you put them. They dial out over WebSocket, so they need no inbound ports and work behind NAT. Each advertises bounded capacity from its own resource governor.
doc-extract. Turns non-HTML bytes into text. Everything heavy in the deployment lives here — PDFium, ONNX Runtime, OpenCV, the OCR weights — which is exactly why the coordinator stays small and the agent stays a single binary.
Three consequences fall out of it:
- Your IPs do the browsing. No shared proxy pool, no third party seeing your traffic.
- Scaling is horizontal and cheap. Add agents. The coordinator does not get busier because you crawl more.
- The blast radius is small. An agent handles hostile pages; the coordinator handling your database never touches one.
Note the arrow from agents to doc-extract: agents call it directly, with bytes they already fetched. The coordinator never does. It only hands each agent the address and secret at connect time.
One SQLite file plus a files directory, both on one volume. No external database, no Redis server — Redis-style needs (rate limits, presence, pub/sub, the scheduler's job store) are met by in-process fakeredis sharing one keyspace.
This is why the coordinator must run as a single worker: a second process gets its own empty keyspace, so token revocation stops working across workers and the scheduler double-fires.
| Path | Purpose |
|---|---|
/api/* |
The REST surface |
/mcp |
Model Context Protocol endpoint |
/ws/ai-gateway |
Agent dispatch WebSocket |
/ws/record |
Live recording relay |
/agent.sh |
Public, secret-free agent installer |
/health |
Unauthenticated health JSON |
/api/about |
Public AGPL §13 source offer |
| everything else | The SPA history fallback |
/agent.sh is declared before the SPA catch-all deliberately — otherwise
curl … | sh would receive the HTML shell with a 200 and pipe a web page into a
shell.
| Path | What it is |
|---|---|
coordinator/ |
Python API and Alembic migrations |
ui/ |
The built SPA the coordinator serves |
frontend/ |
SPA source (shipped for AGPL source availability) |
doc-extract/ |
Document and OCR service |
connectors/writ-mcp/ |
Zero-dependency Node MCP bridge (MIT) |
docker/ |
Dockerfiles, compose, entrypoint |
scripts/ |
gen-env.sh and the CI check scripts |
The Rust agent lives in its own repository,
writ-agent.
usewrit/writ · AGPL-3.0-only · Issues · Discussions · Report a vulnerability
Getting started
Using it
Integrations
Operations
Reference