A local-first desktop research agent that discovers high-value scientific discussions (Reddit-first, plus manual X/Twitter and public web links), scores them, turns selected ones into OpenLabs discussion drafts, lets you review/edit/approve, and posts / replies only after your explicit approval — defaulting to dry-run.
It runs as a native Tauri v2 desktop app. You launch a real app window; the React UI runs inside the Tauri shell and talks to a local Python backend. You never need to open a browser.
Scope, enforced in code: discussions & comments only. The app cannot create OpenLabs projects, vote, create bounties, or trigger funding/staking/stage actions, and it never posts to Reddit/X or sends DMs. See Guardrails.
| Layer | Tech |
|---|---|
| Desktop shell | Tauri v2 (Rust) — native window, notifications, macOS osascript fallback |
| Renderer | React + TypeScript + Vite + Tailwind v4 |
| Backend | Python 3.11 + FastAPI, 127.0.0.1:8787 |
| Database | SQLite via SQLModel (with a storage abstraction for future Supabase) |
| Local model | Ollama — prefilter, classification, dedup, light scoring, summarization |
| Cloud model | Claude API — final scoring, synthesis, draft generation/revision, replies |
| Scheduling | APScheduler — interval discovery runs (default 6h, discovery-only) |
The renderer polls the backend over local HTTP (no websockets). The Tauri window health-gates on
the backend /health endpoint and shows a native setup screen with logs/guidance if it can't connect.
Tauri ships a much smaller binary and uses the OS's native WebView instead of bundling Chromium, so memory/disk footprint is far lower — important for the MacBook Air M1 / 16GB target. The native layer is Rust (fast, safe), and Tauri v2's capability-scoped plugin permissions fit this app's "only do a few specific things" security posture.
- Rust + Cargo (for the Tauri shell): https://www.rust-lang.org/tools/install
- Node 18+
- Python 3.11+ (macOS system Python is 3.9 — install 3.11, e.g.
brew install python@3.11) - Ollama (optional but recommended): https://ollama.com
- macOS Xcode command-line tools for the Tauri build:
xcode-select --install
# 1. Clone / open the project, then copy env
cp .env.example .env # fill in whatever you have; everything is optional for dry-run/demo
# 2. Backend deps (uses Python 3.11)
python3.11 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# 3. Frontend + Tauri deps
npm install # root: Tauri CLI
npm --prefix frontend installollama pull qwen2.5:14b # default local model (configurable in Settings)
ollama pull gemma3:4b # a small fallback works too
ollama serve # or just run the Ollama appThe app checks http://localhost:11434/api/tags at startup. If Ollama is down it degrades to Claude
(if configured) or a deterministic mock scorer.
Put your key in .env:
ANTHROPIC_API_KEY=sk-ant-...
ANTHROPIC_MODEL=claude-sonnet-5
Used for final scoring, drafting, revision, and reply drafting. Without it, Ollama or the mock scorer is used and drafts are generated locally.
Create a script app at https://www.reddit.com/prefs/apps and set (in .env or Settings):
REDDIT_CLIENT_ID=...
REDDIT_CLIENT_SECRET=...
REDDIT_USER_AGENT=ol-research-agent/0.1 by your_reddit_username
No Reddit username/password is required (application-only, read-only). Graceful degradation: with
credentials → full PRAW search; without credentials → a credential-less public JSON path
(www.reddit.com/…/search.json, limited/rate-capped); if even that is unreachable (e.g. Reddit blocks
some datacenter IPs) → deterministic mock candidates. The status bar shows full, limited, or
mock.
OPENLABS_AGENT_TOKEN=...
OPENLABS_DRY_RUN=true
This MVP is dry-run only — see below. The token is never shown in the UI.
Configure all providers in-app at Settings → Credentials (Anthropic, OpenAI, Reddit, X,
Supabase) — or via .env. In-app secrets are stored in a git-ignored data/credentials.json
(chmod 600), layered over .env at runtime; the raw values are never returned to the UI (only masked
status + last-4). Each provider has a Test button that live-validates the key. A generation
provider switch (anthropic | openai) selects which cloud model does scoring/drafting/replies;
it falls back to the other cloud provider, then local Ollama, then a deterministic mock.
Settings → OpenLabs agent runs the full onboarding flow (id.bio.xyz create → onboard → authenticate → verify), or you can paste an existing bearer. See https://openlabs.bio.xyz/signup?mode=agent.
- Dry-run (default): OpenLabs create/reply are simulated — no network write; a local
dry_runrow is stored and a simulated permalink returned. - Live mode: set
OPENLABS_DRY_RUN=false(Settings toggle) and have a valid agent session. Live posting is implemented (LIVE_POSTING_IMPLEMENTED = True) and refreshes the bearer on 401. Even in live mode, nothing posts without your explicit per-item approval.
The confirmed OpenLabs API mapping (from the public OpenAPI) is in
backend/app/connectors/openlabs_api.py: POST /posts (discussion), GET /posts/{id}/comments,
POST /posts/{id}/comments (reply). Only these three operations exist in the client — no
project/vote/bounty/funding methods.
npm run dev # starts backend + Tauri (Tauri also starts Vite)# terminal 1 — backend
npm run dev:backend # = uvicorn on 127.0.0.1:8787 (creates venv on first run)
# terminal 2 — desktop app
npm run dev:tauri # compiles Rust (slow first time), then opens the windowThe first tauri dev compiles the Rust dependencies and can take several minutes on an M1. After
that it's fast. The window shows a setup screen until the backend /health responds, then loads the
full UI automatically.
| Script | Does |
|---|---|
npm run dev |
backend + Tauri together |
npm run dev:backend |
FastAPI via scripts/run-backend.sh |
npm run dev:frontend |
Vite dev server only |
npm run dev:tauri |
Tauri dev (native window) |
npm run build |
build the frontend |
npm run package:mac |
tauri build (macOS bundle) |
- Discovery → Run discovery now. Reddit (or mock) candidates are gathered, pre-scored by Ollama, refined by Claude if available, ranked, and stored. You can also ingest a link manually.
- Candidates → expand a card (rank, source, tags, relevance, risks, suggested angle, related OpenLabs links, recommendation, raw excerpt). Actions: reject / save / mark duplicate / collect material / draft.
- Collect material shows an approval modal — “This will collect material… No posting will happen.” After approval it builds a research brief (source excerpt + public OpenLabs context).
- Draft discussion generates an 8-section OpenLabs draft.
- Drafts → edit in the Markdown editor, save versions, view version history, or ask the agent to revise from feedback. Approve & post shows the final approval modal and posts in dry-run.
- OpenLabs → see your (dry-run) posts, check comments (simulated), draft replies, and approve & post reply (dry-run) behind an approval modal.
- Reports → run/candidate/draft/post/comment stats, which model is in use, and a token-usage bar chart with a day / hour toggle (segmented by provider/model, with a legend).
While any long task runs (discovery, collect, draft, revise), a progress bar appears above the status bar showing the task, the model in use, step x/total, and percent.
- Before collecting material for a candidate.
- Before finalizing/posting a discussion.
- Before posting a reply to a comment.
Nothing is published without an explicit approval click, and an approvals row is recorded for each.
Settings → run interval (default 6h) + auto-discovery toggle. Scheduled runs do discovery only — they never collect material, draft, or post. On completion a desktop notification fires and candidate cards wait for your review.
Settings lets you pick from the key topic list, add free-text objectives, edit subreddits and query terms, set the Ollama/Claude models, toggle dry-run, and manage notifications and storage provider.
Uses the Tauri notification plugin. The frontend polls /notifications and raises a native banner for
anything new (discovery complete, candidates ready, material/draft ready, post result, new comments,
reply needs approval). If the plugin fails, a macOS osascript fallback (native Rust command) is
used — no terminal-notifier required. Test it from Settings → Send test notification.
Enforced in code, not just prompts (backend/app/guardrails.py):
- Blocked (hard stop, raise on attempt): create project · vote · bounty · fund · stake · move project stage · DM · post to X · post to Reddit.
- OpenLabs client implements only
create_discussion,list_comments,reply_to_comment. Project/vote/bounty/funding methods do not exist in the codebase. - Dry-run by default; live posting cannot fire in this MVP.
- No fabrication: prompts forbid inventing citations, links, papers, or OpenLabs entities; related OpenLabs items come only from the public feed or the user. Scoring force-rejects medical-advice, marketing, and political items.
- No medical advice / clinical recommendations.
A storage abstraction exists (backend/app/storage/): LocalSQLiteStore (default) and a
SupabaseStore placeholder. Settings expose storage_provider: sqlite | supabase. Supabase is not
required and not implemented for the MVP.
- OpenLabs live posting / agent onboarding — the 3-step id.bio.xyz create→token→authenticate flow
plus
/auth/agent/onboard+/agent-smokeis not implemented. Dry-run only. - Post permalink — the API returns no slug; we construct a best-effort
/posts/{id}(unverified). - Python sidecar packaging — for a distributable app the backend should be bundled as a Tauri sidecar; MVP documents manual backend start instead.
- OpenLabs
topic_id— left null unless a topic list becomes discoverable.
See PLAN.md §11 and the checklist below. Backend checks:
curl -s 127.0.0.1:8787/health
curl -s 127.0.0.1:8787/status
curl -s -X POST 127.0.0.1:8787/runs/discovery/start
curl -s 127.0.0.1:8787/candidates- backend
/health - DB init + settings read/write
- Ollama availability check
- mock candidate generation without Reddit creds
- manual link ingestion
- draft save/edit/versioning + revise
- dry-run OpenLabs post returns a simulated link
- comment monitoring + reply (dry-run)
- desktop notification test
- Tauri window launches and health-gates on the backend