Status: Research Preview / Experimental Version: v0.1.0-alpha Audience: AI builders, local-LLM users, agent developers, and researchers exploring local-first evidence retrieval.
Warning
This is not production-hardened. Do not expose directly to the public internet without additional review. Default tests currently pass. Live answer-quality evals are optional behind LRS_RUN_LIVE_EVALS=1.
Local Research Service (LRS) is an experimental local-first research backend for builders who want evidence retrieval, bounded research runs, and explicit quality signals instead of opaque agent loops.
- This is a research-preview quality release, not "production ready" or "enterprise grade".
- Local environment assumptions may require adjustment.
- Search quality depends heavily on SearXNG and other provider configurations.
- Live evals depend on external behavior and are opt-in.
- Security hardening exists, but public internet deployment requires a separate review. LRS does not guarantee accuracy or correctness.
Why it exists:
- Keep retrieval and quality checks local, deterministic, and auditable.
- Expose explicit execution and quality outcomes (
termination_reason, gates, quality signals). - Let callers separate retrieval from synthesis when they want a BYO-LLM workflow.
- Provide profile-based behavior from fast retrieval (
quick) to stricter evidence gathering (forensic, experimental).
Start here:
- Try the evidence-first example:
examples/evidence_to_llm.py - Check the canonical API contract:
docs/API_CONTRACT.md - Use the config reference if you are wiring a real local stack:
docs/configuration.md - If you just want to get moving, jump to the Windows quickstart below or
docs/QUICKSTART.md
LRS now ships an optional local operator workbench at GET /workbench.
What it is for:
- local debugging and smoke checks
- demos and benchmarking
- OSS evaluation without hand-writing curl commands
What it is not:
- not the primary product surface
- not an auth-enabled multi-user UI
- not a replacement for the HTTP API
The workbench is intentionally thin. It reads backend-exposed metadata from GET /v1/meta, health from GET /v1/status, and runs searches through the existing research endpoints. By default it now starts on POST /v1/research/simple with output_format="auto" so the main answer stays concise, grounded, and assistant-like; the deeper canonical POST /v1/research path remains reachable from the collapsed Advanced / Testing section instead of dominating the page. It also keeps transparency front-and-center by exposing evidence, raw JSON, and run metadata instead of hiding those details behind frontend-only logic. Degraded/warning semantics now come from the backend-owned termination contract instead of a frontend-maintained warning list. Startup guidance is also backend-declared so the UI does not guess about ports, commands, browser behavior, or which local services are safe to start directly.
The default operator flow is:
- type a query
- choose a search level
- run the search
- inspect evidence or raw/debug tabs only when needed
POST /v1/evidencegives you a retrieval-only evidence bundle instead of forcing synthesis.- Quality signals are explicit: authority, diversity, extraction success, Tier-1 gate state, and termination reason.
- Profiles and budgets are bounded and inspectable instead of being hidden in an agent loop.
GET /v1/statusmakes dependency state visible before you start a long run.
Recommended builder workflow:
- Call
GET /v1/statusto confirm dependencies are reachable. - Call
POST /v1/evidenceto retrieve sources, snippets, and quality signals. - Either synthesize with your own LLM or start with
POST /v1/research/simplefor a fast end-to-end LLM smoke test. - Use
POST /v1/researchwhen you want the deeper bounded loop; it defaults to deep mode and now returns a conciseautoanswer by default instead of a long report.
Default answer guardrails:
output_format="auto"is the normal search-assistant path: concise, direct, and citation-grounded.output_format="structured_report"remains available for testing and debugging when you explicitly want the verbose report shape.- Freshness-sensitive current/date-specific questions fail closed when LRS cannot verify them from sufficiently specific evidence. In those cases the answer will say it could not verify the requested fact instead of presenting an unverified value as definitive.
- Live/current price questions are handled conservatively and currently fail closed unless LRS gains a dedicated verified live-price source contract. Search-result snippets alone do not count as a verified live feed.
Reference example:
examples/evidence_to_llm.pydocs/API_CONTRACT.mddocs/api-reference.md
- Start SearXNG in Docker (WSL):
wsl -e sh -lc "cd <repository-root> && docker compose up -d searxng"- Install and run LRS natively on Windows:
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install --upgrade pip
python -m pip install -e ".[dev]"
$env:LRS_SEARCH__SEARXNG__BASE_URL = "http://127.0.0.1:8080"
$env:LRS_LLM__OPENAI_COMPATIBLE__BASE_URL = "http://127.0.0.1:11434/v1"
$env:LRS_LLM__OPENAI_COMPATIBLE__MODEL = "qwen2.5:14b"
python -m lrsCanonical manual startup command: python -m lrs
Open manually after startup: http://127.0.0.1:8088/workbench
Browser auto-open: no. LRS does not launch a browser for you.
Port override: set LRS_SERVER__PORT or server.port if you need a different bind port.
- Verify runtime mode:
$s = Invoke-RestMethod http://127.0.0.1:8088/v1/status
$s.dependencies.search | Select-Object mode, reachable, base_url
$s.dependencies.llm | Select-Object mode, reachable, route_status, timed_out- Run one bounded synthesized-answer smoke test:
$body = @{
query = "What RFC defines HTTP/3?"
profile = "quick"
overrides = @{
max_sources = 2
max_tokens = 256
}
} | ConvertTo-Json -Depth 4
$r = Invoke-RestMethod -Uri http://127.0.0.1:8088/v1/research/simple -Method Post -ContentType "application/json" -Body $body
$r | Select-Object status, termination_reason, answer
$r.citations | Select-Object -First 2 domain, title- Open the local workbench:
http://127.0.0.1:8088/workbench
The top area now separates app server state from overall system status, so "degraded" means partial capability rather than "the HTTP app is down." The results area exposes answer output, evidence links, raw JSON, and run details in separate sections.
If a local dependency is down, the workbench surfaces backend-declared local service actions. Repo-supported local services such as SearXNG and Ollama can expose one-click starts only when LRS is bound to localhost, direct actions were explicitly enabled with LRS_OPERATOR__ENABLE_DIRECT_ACTIONS=true (or operator.enable_direct_actions=true), and the command path is allowlisted; otherwise the workbench falls back to an exact copyable command or manual guidance. Direct action results are bounded and truthful: the backend now reports already_running, ready, starting_timeout, failed, or unsupported instead of implying success before the configured target becomes reachable.
Dev-only note: direct uvicorn startup is for local experimentation only; it bypasses the python -m lrs port guard and may behave differently.
app.status: whether the LRS HTTP server itself is up.checked_at: when the overall status snapshot was generated.system.status: overall capability state (healthyordegraded).system.summary: human-readable explanation of degraded-vs-healthy system state.dependencies.search.mode: active search backend mode (searxng,demo_search, or custom).dependencies.search.reachable: whether search dependency preflight succeeded.dependencies.search.base_url: effective SearXNG URL when mode issearxng.dependencies.search.timed_out: whether the bounded status probe timed out.dependencies.search.checked_at: when this dependency status was last checked.dependencies.llm.reachable: whether any usable LLM route is currently reachable.dependencies.llm.route_status: whether LRS is using the configured target, a fallback target, or has no reachable LLM route.dependencies.llm.timed_out: whether the bounded LLM status probe timed out.dependencies.llm.checked_at: when this dependency status was last checked.dependencies.llm.configured_target: health of the configured LLM endpoint.dependencies.llm.fallback_target: health of the fallback LLM endpoint, if one was probed.dependencies.llm.active_target: the target currently serving LRS requests.runtime.llm.effective_model: configured model currently in use.service.build_id: short build or git identifier when available.operator.actions: backend-declared local startup help shown when a local dependency is down.
service: app name, version, build id, git sha, and workbench path.runtime.search/runtime.llm: safe runtime metadata for the active backend/provider setup.capabilities.supported_profiles: profile list surfaced to the workbench.capabilities.supported_modes: machine-readable mode-to-endpoint mapping.capabilities.defaults: default mode/profile selections.capabilities.advanced_controls: backend-supported override and output-format controls.contracts.termination_reasons: canonical machine-readable termination semantics, including warning/error and degraded flags.contracts.responses.canonical_evidence_field: backend-declared canonical evidence field the workbench should render.operator.startup.manual_command: the canonical local startup command for the app/workbench.operator.startup.workbench_url: the exact local URL to open after startup.operator.startup.browser_auto_open: whether the startup path launches a browser automatically.operator.execution.direct_actions_enabled: whether one-click local service starts are enabled for this session.operator.execution.reasons: why one-click local service starts are enabled or disabled.operator.actions[*].launch_env_overrides: backend-declared launch environment for allowlisted direct actions when needed.operator.actions[*].console_window_expected: whether a visible console window should appear for that action.
Current direct-start boundary:
Start LRS App: manual-only, because the running workbench cannot truthfully start the server it already depends on.Start search backend: direct from the workbench only for the repo-supported localdocker compose up -d searxngpath, and only when one-click local starts are explicitly enabled for a localhost-bound session.Start Ollama: direct from the workbench only when the configured local LLM endpoint matches the default local Ollama path, and only when one-click local starts are explicitly enabled for a localhost-bound session. On Windows, one-click start runs detached without opening a visible console window, and the backend targets the configured host and port by settingOLLAMA_HOSTbeforeollama serve.
Request:
{
"query": "What is HTTP/3 and what RFC defines it?",
"profile": "balanced",
"max_results": 10,
"include_raw_results": false
}Response (abridged):
{
"query": "What is HTTP/3 and what RFC defines it?",
"status": "ok",
"termination_reason": "ok",
"termination": {
"reason": "ok",
"severity": "info",
"category": "success",
"degraded": false,
"retryable": false,
"summary": "Retrieval completed without backend-reported degradation."
},
"evidence": [
{
"url": "https://datatracker.ietf.org/doc/rfc9114/",
"title": "RFC 9114: HTTP/3",
"domain": "datatracker.ietf.org",
"snippet": "HTTP/3 is the third major version...",
"extracted_content": "optional extracted plain text...",
"source_type": "tier1",
"retrieved_via": "bing"
}
],
"stats": {
"results_returned": 10,
"results_selected": 8,
"tier1_count": 2
},
"evidence_quality_signals": {
"authority_mean": 0.81,
"domain_diversity": 0.88,
"fallback_engine_used": false,
"selection_fallback_used": false,
"refinement_used": false,
"extracted_items_count": 2,
"extracted_total_chars": 6000,
"extraction_failures_count": 0
},
"gate_results": {
"tier1_passed": true
}
}| Profile | Promise | Tier1 gate target | Refinement | Extraction budget |
|---|---|---|---|---|
quick |
fastest retrieval-only bundle | 0 | disabled | K=0, 0 chars |
balanced |
practical authority/diversity baseline | 1 | disabled | K=2, 6000 total chars |
rigorous |
stricter authority requirements | 2 | opt-in via LRS_EVIDENCE_REFINEMENT_ENABLE_RIGOROUS=true |
K=3, 12000 total chars |
forensic |
most exhaustive profile (experimental in Phase 1) | 2 | enabled by default | K=4, 24000 total chars |
Refinement budget (when enabled): up to 2 additional attempts within a 12 second window.
ok: retrieval completed and quality checks did not trigger degradation.low_quality: evidence returned, but quality degradation signals were detected.gate_failed: evidence returned, but required quality gate(s) failed (for example Tier1 shortfall).search_failed: retrieval execution failed.search_unavailable: upstream search dependency unreachable/unhealthy.no_results: retrieval ran but produced no usable evidence.
Every research and evidence response now also includes a machine-readable termination object. The workbench should treat that object as the canonical degraded/warning contract and treat termination_reason as the stable identifier.
authority_mean: mean authority score of selected evidence domains.domain_diversity: unique selected domains divided by selected evidence count.off_locale_filtered_count: candidates removed by locale/off-locale guard.fallback_engine_used/fallback_engine_name: whether fallback search engine path was used and which engine.locale_retries: provider locale retry count.selection_fallback_used: selector had to use its fallback path.refinement_used/refinement_attempts/planned_queries_count: refinement loop observability.first_attempt_*andbest_attempt_*: comparable before/after attempt summary metrics.extracted_items_count/extracted_total_chars/extraction_failures_count: post-selection page extraction observability.
Tier1 counts are exposed in stats.tier1_count and gate_results.tier1_count.
Tier1 allowlists are code-defined in src/lrs/utils/tier1_domains.py:
- Tier1-A: standards, government, and primary authorities.
- Tier1-B: official vendor/project/protocol documentation.
Matching is host-based with suffix support (foo.nasa.gov matches nasa.gov).
To add a Tier1 domain:
- Add it to the appropriate allowlist (
TIER1A_DOMAIN_ALLOWLISTorTIER1B_DOMAIN_ALLOWLIST). - Add/adjust tests in
tests/unit/test_tier1_domains.py. - Include rationale in PR notes (primary authority vs official project docs).
docs/QUICKSTART.mddocs/API_CONTRACT.mddocs/configuration.mddocs/api-reference.mddocs/BENCHMARKS.mddocs/providers.mddocs/CONTRIBUTING.mdSECURITY.md
The operator workbench is intended for local use on trusted machines. It has no auth layer and surfaces runtime metadata plus raw request/response data for transparency. Keep LRS bound to localhost unless you add your own network controls in front of it.