Systole is a local-first research workflow app:
- Daily digest: fetches arXiv papers, scores relevance, and stores per-day feeds.
- Summary generation: creates daily markdown summaries on demand.
- Knowledge archive: ingests screenshots/links/text into structured
paper | software | article | datasetitems.
The app is split into a Vue frontend and a FastAPI backend, with JSON/JSONL file storage under data/.
frontend/ Vue 3 + TypeScript + Vite
backend/ FastAPI app + ingest pipeline
systole/api.py HTTP API routes
systole/tasks.py daily fetch task orchestration
systole/archive/ ingest/extraction/archive modules
config.toml.example backend config template
scripts/run_dev.sh one-command local dev (backend + frontend)
data/ runtime files (git-ignored)
- Python
>= 3.12 - Node.js
>= 18 uv(recommended Python package manager)opencodeCLI (required for archive ingestion)
- Clone and enter the repo.
git clone <repo-url> Systole
cd Systole- Create backend config.
cp backend/config.toml.example backend/config.tomlEdit backend/config.toml and fill at least:
[llm.<provider>]credentials (base_url,api_key,model_name)[llm_select]provider names forsummary,relevance, andvision[opencode]binary path and timeout
- Start everything.
scripts/run_dev.shDefault dev URLs:
- Frontend:
http://127.0.0.1:15173 - Backend:
http://127.0.0.1:18080
Optional backend auto-reload:
SYSTOLE_BACKEND_RELOAD=1 scripts/run_dev.shBackend:
cd backend
uv venv .venv --python 3.12
uv sync
cp config.toml.example config.toml
uv run python -m systoleFrontend:
cd frontend
npm install
VITE_API_BASE_URL=http://127.0.0.1:18080 npm run devSystole uses two config layers:
- Backend runtime config (
backend/config.toml) - User settings (
data/settings.json, managed via/settingsAPI/UI)
Loaded by backend/systole/config.py (default path: config.toml in current working directory).
You can override config path:
SYSTOLE_CONFIG=/absolute/path/to/config.toml uv run python -m systoleKey sections:
[llm.<name>]: provider definitions (OpenAI-compatible/chat/completions)[llm_select]: which provider name to use for summary/relevance/vision[opencode]: OpenCode executable and timeout[ingest]: optionalmax_concurrencyfor ingest worker semaphore[system]: app-level metadata
Archive ingestion endpoints call opencode as a subprocess. If opencode is missing or misconfigured, ingestion fails while daily fetch APIs still work.
- Trigger fetch for "today" (Beijing time):
POST /tasks/daily - Fetch writes paper records into
data/YYYY-MM-DD/*.jsonl - Relevance is scored during the task (LLM with heuristic fallback on request failures)
- Summary is generated separately (manual trigger)
- Trigger:
POST /daily/{date}/summary/generate - Check task:
GET /daily/{date}/summary/task - Read summary:
GET /daily/{date}/summary
Input sources supported by /archive/ingest/upload:
- Text (plain text, URLs, snippets)
- Image upload (
.png,.jpg,.jpeg,.webp) - Combined image + text context
Processing is async: upload endpoints return quickly with item_id, then status is polled via inbox endpoints.
GET /healthGET /datesGET /settingsPOST /settingsPOST /tasks/dailyGET /tasksGET /tasks/{task_id}POST /tasks/{task_id}/cancel(currently returns410, cancel disabled)GET /daily/{date}DELETE /daily/{date}/items/{source}/{source_id}GET /daily/{date}/summaryGET /daily/{date}/summary/taskPOST /daily/{date}/summary/generate
GET /archive/itemsGET /archive/items/{item_id}PUT /archive/items/{item_id}DELETE /archive/items/{item_id}
POST /archive/ingest/uploadPOST /archive/ingest/from-dailyPOST /archive/ingest/batchGET /archive/ingest/statusGET /archive/inboxGET /archive/inbox/{item_id}GET /archive/inbox/{item_id}/preview(image items only)POST /archive/inbox/{item_id}/retry
POST /archive/ingest/batch-upload(browser folder upload flow)POST /archive/ingest/batch-folder(server-local folder path)GET /archive/ingest/batchesGET /archive/ingest/batch/{batch_id}POST /archive/ingest/batch/{batch_id}/retry-failed
Runtime data is file-based:
data/
settings.json
tasks/
*.json # background task states
YYYY-MM-DD/
*.jsonl # daily source files (arXiv etc.)
summary.json
archive/
items.jsonl # current archive item store
papers.jsonl # legacy fallback read path
inbox.jsonl
inbox/
uploads/ # uploaded files
batch_jobs/
*.json # per-batch status
OpenCode not found:
which opencode
opencode --versionIf needed, set explicit binary path in backend/config.toml:
[opencode]
bin = "/absolute/path/to/opencode"
timeout = 1800Backend config not loading:
- Ensure
backend/config.tomlexists. - If running from repo root with manual backend commands, set
SYSTOLE_CONFIG=backend/config.toml.
Frontend cannot reach backend:
- Set
VITE_API_BASE_URLand restartnpm run dev. - Confirm backend is running on
127.0.0.1:18080.
Frontend:
cd frontend
npm run type-check
npm run buildBackend test files live under backend/tests/ and currently require installing pytest manually.