Commands
serve.py
A local web page for everything you would do by hand: browse projects, review and edit rules, and run the scripts. It reads and writes the same files as the command line, so the two always agree.
Synopsis
python3 serve.py # prints the address and opens your browser
python3 serve.py --port 9000 --no-open
| Option | Default | Meaning |
|---|---|---|
--port PORT | 8765 | The port on 127.0.0.1. Exits with an error if it cannot listen there. |
--no-open | off | Do not open the browser. |
lore UI: http://127.0.0.1:8765/?t=Zk3q…
Data: /home/you/.lore
Ctrl+C to stop.
?t=<token>. The token is new on every run, and the bare address shows only "Open the address printed by serve.py". Open it with 127.0.0.1, not localhost: the name localhost is refused on purpose.A tour
The project list
Registered projects are listed on the left, each with a badge for how many rules wait for review. + Add project at the bottom runs memorize.py for a folder you type (the name is optional) and opens the new project. Next to the project's name a chip says your repository or not your repository, the last trust decision.
The rules: Pending and Approved
Each rule is a card with its text, scope, source, evidence and links to what justifies it. Chips show what Lore knows:
- 📌 always sent: the rule is sent with every request.
- auto-activated: Lore activated it by itself on that date; nobody approved it.
- 🏷 title: the agent's tags; hover to see the trigger words.
- ⚠ looks risky and ⚠ hidden characters: see below.
- For approved rules, how often the agent received it ("served 12× · last 2026-10-17") or "never served".
Actions per rule: Approve (pending rules), Edit (text and scope, with section names suggested), Pin/Unpin (approved rules), Unapprove, and Reject/Delete. + Add rule creates a rule as approved or pending. A filter box searches the text, scope and source.
- Two clicks for anything destructive or wide. "Approve all", delete and bulk actions ask for a second click and disarm after a few seconds.
- Risky rules need a deliberate approval. A rule that looks like an attack shows a red chip and its button reads Review & approve, then Approve anyway?. "Approve all" skips flagged rules and says how many it skipped.
- Hidden characters block approval. The rule is shown with
<U+XXXX>markers and its Approve button is disabled until you edit or reject it.
The Tools tab
Every script, with its output on the page: the exact command that ran, its exit status and how long it took.
| Card | Runs |
|---|---|
| Discover conventions | discover.py: preview, save as pending, or save and approve; adjustable thresholds. |
| Learn from PR reviews | learn.py: preview or save; name the pull requests or leave blank for recent ones. Needs gh. |
| Train on instruction files | train.py: a folder or file, optional forced scope, pending and dry-run options. |
| What will the agent see? | review.py resolve: the exact rules for the files you list (up to 20). |
| Project | memorize.py (re-map), review.py sync (write Copilot instruction files), and a check that mcp_server.py answers the MCP handshake. |
How it is built
A single page (web/index.html) plus a small JSON API served by Python's standard library. The page is read from disk on every request and sent with Cache-Control: no-store, so a reload always shows the current version and editing the page needs no restart. Changes to the Python code do need a restart.
| Method and path | Does |
|---|---|
GET / | The page. |
GET /api/projects | Projects with their rule counts. |
GET /api/projects/<name>/rules | One project: sections, trust, and all rules. |
POST /api/projects | Register a project (memorize.py). |
POST /api/projects/<name>/rules | Add a rule. |
POST /api/projects/<name>/rules/<id> | An action on one rule: approve, unapprove, pin, unpin, edit, remove. |
POST /api/projects/<name>/run | Run one of the fixed tools. |
The server code lives in web/: server.py (HTTP and security checks), api.py (what the page can do, testable without HTTP), runner.py (runs scripts) and validation.py. See architecture.
Security
The server can run Lore's scripts on folders you name, so it is locked down:
- It listens on
127.0.0.1only, never on other network interfaces. - Every API call needs the random token (
X-Lore-Token), different on every run. Requests whoseHostorOriginis not127.0.0.1:<port>are refused, which defeats DNS rebinding and other websites calling it. - Request bodies must be JSON objects of at most 64 KB.
- The page can run only the fixed set of Lore scripts above, with arguments passed as a list (never through a shell), one at a time, with a 120-second limit and output capped at 100,000 characters.
- Rule text and script output are always rendered as plain text, never as HTML, under a content security policy that allows no outside requests.
It is meant for your own machine. Other programs running as you can reach the port, and anything that can read the token can use it. Do not expose it. Stop it with Ctrl+C.