# AGENTS.md — project law for dsh (DeepSeek Harness) ## What this is A **story layer** that sits next to a stock FreshRSS reader (Docker) and turns its syndicated feed items into ranked, deduplicated, optionally-LLM-summarized "stories". FreshRSS + RSSHub run in Docker. Everything in this repo is the thin Python layer on the host that does fetch → cluster → rank → optional LLM. ## Hard rules (violating these fails the task) 1. **Keep the test suite green.** `pytest` in `prototype/` must pass before and after every change. The core accept is "two reworded copies → one cluster". 2. **Do NOT rebuild FreshRSS.** It is a stock container. No fork, no replacement, no patches to it. 3. **Do NOT add Kafka** (or any broker). Async workers + SQLite/JSON is the ceiling. 4. **Do NOT add a frontend framework.** `prototype/out/index.html` is a generated, self-contained static page. No React/Vue/Next/HTMX build tooling. 5. **Cap the LLM.** Only the top-k ranked stories (default 10, flag `--topk`) get summarized; input is headline + first ~300 chars. Never the long tail. 6. **Story summaries point at the HOST Hermes/llama.cpp, never at DeepSeek.** `LLM_BASE_URL` defaults to `http://127.0.0.1:8080/v1`, model `hermes`. Coding work may use DeepSeek; news summaries must not — unless the operator explicitly overrides `LLM_BASE_URL`. 7. **Do not change ports.** FreshRSS = 8280, RSSHub = 1200, Hermes = 8080, `--serve` = 8765. 8280 exists so FreshRSS does not steal llama.cpp's 8080. ## Feature backlog (already spec'd — just implement) - [ ] `cluster.py --serve :8765` — serve `prototype/out/` on port 8765. - [ ] LLM top-k cap — enforce `--topk` everywhere (done in current code; keep it). - [ ] Snapshot mode — `--snapshot JSON` ingests a FreshRSS export with no Docker. ## Interfaces - `python cluster.py [--opml feeds.opml | --snapshot x.json] [--llm] [--topk N] [--serve :8765]` - Env (only for `--llm`): `LLM_BASE_URL`, `LLM_MODEL`. - Output: `prototype/out/index.html` + `prototype/out/stories.json`.