Skip to content

Repository files navigation

LRS (Local Research Service)

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.

Known Limitations

  • 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

Thin Operator Workbench

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

Why Use This Instead Of A Generic Deep Research Wrapper?

  • POST /v1/evidence gives 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/status makes dependency state visible before you start a long run.

Evidence-First Builder Flow

Recommended builder workflow:

  1. Call GET /v1/status to confirm dependencies are reachable.
  2. Call POST /v1/evidence to retrieve sources, snippets, and quality signals.
  3. Either synthesize with your own LLM or start with POST /v1/research/simple for a fast end-to-end LLM smoke test.
  4. Use POST /v1/research when you want the deeper bounded loop; it defaults to deep mode and now returns a concise auto answer 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.py
  • docs/API_CONTRACT.md
  • docs/api-reference.md

Quickstart: Windows LRS + Docker SearXNG

  1. Start SearXNG in Docker (WSL):
wsl -e sh -lc "cd <repository-root> && docker compose up -d searxng"
  1. 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 lrs

Canonical 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.

  1. 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
  1. 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
  1. 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.

/v1/status fields that matter

  • app.status: whether the LRS HTTP server itself is up.
  • checked_at: when the overall status snapshot was generated.
  • system.status: overall capability state (healthy or degraded).
  • 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 is searxng.
  • 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.

/v1/meta fields that matter

  • 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 local docker compose up -d searxng path, 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 setting OLLAMA_HOST before ollama serve.

/v1/evidence contract (compact)

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
  }
}

Profiles: promises and budgets

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.

termination_reason meanings for /v1/evidence

  • 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.

evidence_quality_signals meanings

  • 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_* and best_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-A vs Tier1-B governance

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:

  1. Add it to the appropriate allowlist (TIER1A_DOMAIN_ALLOWLIST or TIER1B_DOMAIN_ALLOWLIST).
  2. Add/adjust tests in tests/unit/test_tier1_domains.py.
  3. Include rationale in PR notes (primary authority vs official project docs).

Additional docs

  • docs/QUICKSTART.md
  • docs/API_CONTRACT.md
  • docs/configuration.md
  • docs/api-reference.md
  • docs/BENCHMARKS.md
  • docs/providers.md
  • docs/CONTRIBUTING.md
  • SECURITY.md

Localhost and Security Caveat

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.

About

Experimental local-first evidence retrieval backend for AI agents and local LLM workflows.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages