lore-llm 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. lore_llm-0.1.0/.github/workflows/catalog-refresh.yml +44 -0
  2. lore_llm-0.1.0/.github/workflows/ci.yml +36 -0
  3. lore_llm-0.1.0/.github/workflows/docker.yml +43 -0
  4. lore_llm-0.1.0/.github/workflows/release.yml +73 -0
  5. lore_llm-0.1.0/.gitignore +29 -0
  6. lore_llm-0.1.0/.pre-commit-config.yaml +15 -0
  7. lore_llm-0.1.0/CLAUDE.md +333 -0
  8. lore_llm-0.1.0/PKG-INFO +225 -0
  9. lore_llm-0.1.0/README.md +203 -0
  10. lore_llm-0.1.0/deploy/Dockerfile +26 -0
  11. lore_llm-0.1.0/deploy/README.md +67 -0
  12. lore_llm-0.1.0/deploy/compose.yaml +49 -0
  13. lore_llm-0.1.0/docs/RELEASING.md +53 -0
  14. lore_llm-0.1.0/docs/ROADMAP.md +40 -0
  15. lore_llm-0.1.0/docs/design/0001-cli-as-thin-client.md +28 -0
  16. lore_llm-0.1.0/docs/design/0002-client-side-provisioning.md +40 -0
  17. lore_llm-0.1.0/docs/design/0003-generic-analysis-task-engine.md +49 -0
  18. lore_llm-0.1.0/docs/design/frontend/README.md +155 -0
  19. lore_llm-0.1.0/docs/design/frontend/Sage Frontend Mock.dc.html +808 -0
  20. lore_llm-0.1.0/docs/design/frontend/original_design_brief.md +146 -0
  21. lore_llm-0.1.0/docs/design/frontend/support.js +1687 -0
  22. lore_llm-0.1.0/docs/lessons.md +33 -0
  23. lore_llm-0.1.0/pyproject.toml +80 -0
  24. lore_llm-0.1.0/scripts/refresh_model_catalog.py +88 -0
  25. lore_llm-0.1.0/src/lore/__init__.py +13 -0
  26. lore_llm-0.1.0/src/lore/cli/__init__.py +5 -0
  27. lore_llm-0.1.0/src/lore/cli/app.py +1061 -0
  28. lore_llm-0.1.0/src/lore/cli/client.py +237 -0
  29. lore_llm-0.1.0/src/lore/config/__init__.py +19 -0
  30. lore_llm-0.1.0/src/lore/config/loader.py +121 -0
  31. lore_llm-0.1.0/src/lore/config/paths.py +81 -0
  32. lore_llm-0.1.0/src/lore/config/schemas.py +224 -0
  33. lore_llm-0.1.0/src/lore/models_catalog.yaml +137 -0
  34. lore_llm-0.1.0/src/lore/py.typed +0 -0
  35. lore_llm-0.1.0/src/lore/server/__init__.py +10 -0
  36. lore_llm-0.1.0/src/lore/server/__main__.py +38 -0
  37. lore_llm-0.1.0/src/lore/server/api/__init__.py +5 -0
  38. lore_llm-0.1.0/src/lore/server/api/analyze.py +59 -0
  39. lore_llm-0.1.0/src/lore/server/api/chat.py +47 -0
  40. lore_llm-0.1.0/src/lore/server/api/health.py +24 -0
  41. lore_llm-0.1.0/src/lore/server/api/instances.py +137 -0
  42. lore_llm-0.1.0/src/lore/server/api/kb.py +141 -0
  43. lore_llm-0.1.0/src/lore/server/api/memory.py +97 -0
  44. lore_llm-0.1.0/src/lore/server/api/models.py +117 -0
  45. lore_llm-0.1.0/src/lore/server/api/system.py +49 -0
  46. lore_llm-0.1.0/src/lore/server/app.py +83 -0
  47. lore_llm-0.1.0/src/lore/server/core/__init__.py +1 -0
  48. lore_llm-0.1.0/src/lore/server/core/analyze.py +289 -0
  49. lore_llm-0.1.0/src/lore/server/core/chat.py +301 -0
  50. lore_llm-0.1.0/src/lore/server/core/instances.py +69 -0
  51. lore_llm-0.1.0/src/lore/server/core/kb.py +65 -0
  52. lore_llm-0.1.0/src/lore/server/core/tasks.py +71 -0
  53. lore_llm-0.1.0/src/lore/server/lifecycle.py +153 -0
  54. lore_llm-0.1.0/src/lore/server/memory/__init__.py +1 -0
  55. lore_llm-0.1.0/src/lore/server/memory/service.py +126 -0
  56. lore_llm-0.1.0/src/lore/server/providers/__init__.py +5 -0
  57. lore_llm-0.1.0/src/lore/server/providers/base.py +80 -0
  58. lore_llm-0.1.0/src/lore/server/providers/ollama.py +134 -0
  59. lore_llm-0.1.0/src/lore/server/providers/openai_compat.py +116 -0
  60. lore_llm-0.1.0/src/lore/server/providers/registry.py +36 -0
  61. lore_llm-0.1.0/src/lore/server/rag/__init__.py +5 -0
  62. lore_llm-0.1.0/src/lore/server/rag/bm25.py +55 -0
  63. lore_llm-0.1.0/src/lore/server/rag/chunking.py +82 -0
  64. lore_llm-0.1.0/src/lore/server/rag/code.py +159 -0
  65. lore_llm-0.1.0/src/lore/server/rag/ingest.py +136 -0
  66. lore_llm-0.1.0/src/lore/server/rag/parsers.py +175 -0
  67. lore_llm-0.1.0/src/lore/server/rag/retrieval.py +72 -0
  68. lore_llm-0.1.0/src/lore/server/rag/types.py +61 -0
  69. lore_llm-0.1.0/src/lore/server/state.py +51 -0
  70. lore_llm-0.1.0/src/lore/server/verify/__init__.py +6 -0
  71. lore_llm-0.1.0/src/lore/server/verify/grounding.py +137 -0
  72. lore_llm-0.1.0/src/lore/server/verify/self_consistency.py +122 -0
  73. lore_llm-0.1.0/src/lore/setup/__init__.py +7 -0
  74. lore_llm-0.1.0/src/lore/setup/catalog.py +83 -0
  75. lore_llm-0.1.0/src/lore/setup/doctor.py +155 -0
  76. lore_llm-0.1.0/src/lore/setup/hardware.py +102 -0
  77. lore_llm-0.1.0/src/lore/setup/ollama_setup.py +153 -0
  78. lore_llm-0.1.0/src/lore/setup/recommend.py +58 -0
  79. lore_llm-0.1.0/src/lore/store/__init__.py +22 -0
  80. lore_llm-0.1.0/src/lore/store/db.py +112 -0
  81. lore_llm-0.1.0/src/lore/store/repository.py +287 -0
  82. lore_llm-0.1.0/src/lore/store/vector.py +144 -0
  83. lore_llm-0.1.0/src/lore/tasks/__init__.py +6 -0
  84. lore_llm-0.1.0/src/lore/tasks/aou-gap.yaml +19 -0
  85. lore_llm-0.1.0/src/lore/tasks/requirements-coverage.yaml +16 -0
  86. lore_llm-0.1.0/src/lore/tasks/test-gap.yaml +16 -0
  87. lore_llm-0.1.0/tests/__init__.py +0 -0
  88. lore_llm-0.1.0/tests/conftest.py +15 -0
  89. lore_llm-0.1.0/tests/embeddings.py +30 -0
  90. lore_llm-0.1.0/tests/fake_openai.py +93 -0
  91. lore_llm-0.1.0/tests/fixtures/README.md +11 -0
  92. lore_llm-0.1.0/tests/integration/README.md +11 -0
  93. lore_llm-0.1.0/tests/integration/__init__.py +0 -0
  94. lore_llm-0.1.0/tests/integration/test_grounded_live.py +94 -0
  95. lore_llm-0.1.0/tests/integration/test_ollama_live.py +59 -0
  96. lore_llm-0.1.0/tests/unit/__init__.py +0 -0
  97. lore_llm-0.1.0/tests/unit/test_analyze.py +261 -0
  98. lore_llm-0.1.0/tests/unit/test_api.py +469 -0
  99. lore_llm-0.1.0/tests/unit/test_auth.py +107 -0
  100. lore_llm-0.1.0/tests/unit/test_catalog.py +198 -0
  101. lore_llm-0.1.0/tests/unit/test_cli.py +71 -0
  102. lore_llm-0.1.0/tests/unit/test_cli_e2e.py +145 -0
  103. lore_llm-0.1.0/tests/unit/test_code_ingestion.py +125 -0
  104. lore_llm-0.1.0/tests/unit/test_config.py +113 -0
  105. lore_llm-0.1.0/tests/unit/test_doctor.py +125 -0
  106. lore_llm-0.1.0/tests/unit/test_grounding_verify.py +185 -0
  107. lore_llm-0.1.0/tests/unit/test_ingest.py +110 -0
  108. lore_llm-0.1.0/tests/unit/test_lifecycle.py +56 -0
  109. lore_llm-0.1.0/tests/unit/test_memory.py +199 -0
  110. lore_llm-0.1.0/tests/unit/test_providers.py +166 -0
  111. lore_llm-0.1.0/tests/unit/test_rag_pipeline.py +180 -0
  112. lore_llm-0.1.0/tests/unit/test_self_consistency.py +152 -0
  113. lore_llm-0.1.0/tests/unit/test_server.py +25 -0
  114. lore_llm-0.1.0/tests/unit/test_setup.py +139 -0
  115. lore_llm-0.1.0/tests/unit/test_store.py +163 -0
  116. lore_llm-0.1.0/uv.lock +3101 -0
@@ -0,0 +1,44 @@
1
+ name: Refresh model catalog
2
+
3
+ on:
4
+ schedule:
5
+ - cron: "17 6 * * 1" # weekly, Monday 06:17 UTC
6
+ workflow_dispatch: {}
7
+
8
+ jobs:
9
+ refresh:
10
+ runs-on: ubuntu-latest
11
+ permissions:
12
+ contents: write
13
+ pull-requests: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v5
17
+ with:
18
+ python-version: "3.11"
19
+ - name: Check catalog entries against the Ollama registry
20
+ id: refresh
21
+ run: |
22
+ set +e
23
+ uv run --with httpx --with pyyaml python scripts/refresh_model_catalog.py
24
+ code=$?
25
+ set -e
26
+ if [ "$code" = "2" ]; then
27
+ echo "registry unreachable; skipping this run"
28
+ echo "changed=false" >> "$GITHUB_OUTPUT"
29
+ exit 0
30
+ fi
31
+ echo "changed=$([ "$code" = "1" ] && echo true || echo false)" >> "$GITHUB_OUTPUT"
32
+ - name: Open update PR
33
+ if: steps.refresh.outputs.changed == 'true'
34
+ uses: peter-evans/create-pull-request@v6
35
+ with:
36
+ branch: bot/catalog-refresh
37
+ title: "Model catalog refresh: registry availability changed"
38
+ commit-message: "Refresh model catalog against the Ollama registry"
39
+ body: >
40
+ Automated weekly check of `src/lore/models_catalog.yaml` against
41
+ the Ollama registry. Entries whose tags no longer resolve are
42
+ marked `available: false` (and vice versa). Review and merge —
43
+ the catalog is curated, never silently self-modified.
44
+ labels: automated
@@ -0,0 +1,36 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ lint:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v5
14
+ with:
15
+ python-version: "3.11"
16
+ - run: uv sync --dev
17
+ - name: ruff lint
18
+ run: uv run ruff check .
19
+ - name: ruff format
20
+ run: uv run ruff format --check .
21
+ - name: mypy (strict)
22
+ run: uv run mypy
23
+
24
+ test:
25
+ runs-on: ubuntu-latest
26
+ strategy:
27
+ matrix:
28
+ python-version: ["3.11", "3.12"]
29
+ steps:
30
+ - uses: actions/checkout@v4
31
+ - uses: astral-sh/setup-uv@v5
32
+ with:
33
+ python-version: ${{ matrix.python-version }}
34
+ - run: uv sync --dev
35
+ - name: unit tests (integration tests requiring Ollama are skipped)
36
+ run: uv run pytest -m "not requires_ollama" -q
@@ -0,0 +1,43 @@
1
+ name: Docker image
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ tags: ["v*"]
7
+ pull_request:
8
+ paths: ["deploy/**", "src/**", "pyproject.toml"]
9
+
10
+ jobs:
11
+ image:
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: read
15
+ packages: write
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: docker/setup-buildx-action@v3
19
+ - name: Log in to GHCR
20
+ if: github.event_name != 'pull_request'
21
+ uses: docker/login-action@v3
22
+ with:
23
+ registry: ghcr.io
24
+ username: ${{ github.actor }}
25
+ password: ${{ secrets.GITHUB_TOKEN }}
26
+ - name: Image metadata
27
+ id: meta
28
+ uses: docker/metadata-action@v5
29
+ with:
30
+ images: ghcr.io/${{ github.repository }}
31
+ tags: |
32
+ type=ref,event=tag
33
+ type=raw,value=latest,enable={{is_default_branch}}
34
+ - name: Build (and push on main/tags)
35
+ uses: docker/build-push-action@v6
36
+ with:
37
+ context: .
38
+ file: deploy/Dockerfile
39
+ push: ${{ github.event_name != 'pull_request' }}
40
+ tags: ${{ steps.meta.outputs.tags }}
41
+ labels: ${{ steps.meta.outputs.labels }}
42
+ cache-from: type=gha
43
+ cache-to: type=gha,mode=max
@@ -0,0 +1,73 @@
1
+ name: Release
2
+
3
+ # Cut a release by pushing a version tag (see docs/RELEASING.md):
4
+ # git tag v0.1.0 && git push origin v0.1.0
5
+ # Pipeline: full checks → build → publish to PyPI (trusted publishing,
6
+ # no tokens) → GitHub release with the artifacts attached.
7
+
8
+ on:
9
+ push:
10
+ tags: ["v*"]
11
+
12
+ jobs:
13
+ checks:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v5
18
+ with:
19
+ python-version: "3.11"
20
+ - run: uv sync --dev
21
+ - run: uv run ruff check .
22
+ - run: uv run ruff format --check .
23
+ - run: uv run mypy
24
+ - run: uv run pytest -m "not requires_ollama" -q
25
+ - name: Tag must match the project version
26
+ run: |
27
+ v=$(uv run python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
28
+ if [ "v$v" != "$GITHUB_REF_NAME" ]; then
29
+ echo "::error::tag $GITHUB_REF_NAME does not match pyproject version v$v"
30
+ exit 1
31
+ fi
32
+
33
+ build:
34
+ needs: checks
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - uses: astral-sh/setup-uv@v5
39
+ with:
40
+ python-version: "3.11"
41
+ - run: uv build
42
+ - uses: actions/upload-artifact@v4
43
+ with:
44
+ name: dist
45
+ path: dist/
46
+
47
+ publish-pypi:
48
+ needs: build
49
+ runs-on: ubuntu-latest
50
+ environment: pypi
51
+ permissions:
52
+ id-token: write # OIDC for PyPI trusted publishing — no API tokens
53
+ steps:
54
+ - uses: actions/download-artifact@v4
55
+ with:
56
+ name: dist
57
+ path: dist/
58
+ - uses: pypa/gh-action-pypi-publish@release/v1
59
+
60
+ github-release:
61
+ needs: publish-pypi
62
+ runs-on: ubuntu-latest
63
+ permissions:
64
+ contents: write
65
+ steps:
66
+ - uses: actions/download-artifact@v4
67
+ with:
68
+ name: dist
69
+ path: dist/
70
+ - uses: softprops/action-gh-release@v2
71
+ with:
72
+ files: dist/*
73
+ generate_release_notes: true
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+
9
+ # Environments
10
+ .venv/
11
+ venv/
12
+
13
+ # Tooling caches
14
+ .mypy_cache/
15
+ .ruff_cache/
16
+ .pytest_cache/
17
+ .coverage
18
+ htmlcov/
19
+
20
+ # Editors
21
+ .idea/
22
+ .vscode/
23
+ *.swp
24
+
25
+ # Lore runtime state (never commit a live home dir)
26
+ .lore/
27
+ *.db
28
+ *.db-wal
29
+ *.db-shm
@@ -0,0 +1,15 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v4.6.0
4
+ hooks:
5
+ - id: end-of-file-fixer
6
+ - id: trailing-whitespace
7
+ - id: check-yaml
8
+ - id: check-added-large-files
9
+
10
+ - repo: https://github.com/astral-sh/ruff-pre-commit
11
+ rev: v0.5.7
12
+ hooks:
13
+ - id: ruff
14
+ args: [--fix]
15
+ - id: ruff-format
@@ -0,0 +1,333 @@
1
+ # Project: Sage — Local-First LLM Instance Framework
2
+
3
+ > **RENAMED (2026-07-06): the framework is now "Lore"** — CLI command `lore`,
4
+ > PyPI distribution `lore-llm`, home dir `~/.lore`, default port 5673
5
+ > (L-O-R-E on a phone keypad), env vars `LORE_*`. "Sage" below is the
6
+ > historical working name; this document is kept as the original design spec.
7
+ > Current sequencing lives in `docs/ROADMAP.md`.
8
+
9
+ > **Working name.** "Sage" is a placeholder — rename freely. One word, easy to type as a CLI command.
10
+
11
+ This document is the authoritative context for implementing Sage. Read it fully before writing any code. It defines the vision, architecture, design decisions (with rationale), the phased PR plan, and the engineering conventions this project must follow.
12
+
13
+ ---
14
+
15
+ ## 1. Vision
16
+
17
+ Sage is a framework for deploying, managing, and specializing LLM instances — locally first, cloud optionally — without sending private data to commercial AI providers or paying per-token.
18
+
19
+ A user should be able to:
20
+
21
+ 1. **Deploy a model with one command**, locally (`sage up`) or to a cloud target (`sage deploy --target <cloud>`).
22
+ 2. **Create named, specialized instances** of a model, each grounded in a private knowledge base (datasheets, source code, AOU documents, coding standards).
23
+ 3. **Chat with an instance** from the CLI or a web UI, with responses grounded in and cited against the instance's knowledge base.
24
+ 4. **Configure response strictness** — a confidence/grounding threshold below which the instance must refuse, hedge, or flag uncertainty instead of answering.
25
+ 5. **Retain memory per instance across runs** (local or cloud), with explicit user-controlled deletion.
26
+ 6. **Tweak everything via YAML** (CLI-first), with full feature parity in the web frontend.
27
+
28
+ ### Motivating use cases (acceptance scenarios)
29
+
30
+ These three scenarios define "done" for the MVP. Every architectural decision should be sanity-checked against them.
31
+
32
+ - **UC-1: Datasheet expert.** Point an instance at a processor datasheet set (e.g., Qualcomm SA8775 docs, multi-thousand-page PDFs). Ask detailed technical questions; get accurate, cited answers with page/section references.
33
+ - **UC-2: AOU gap analysis.** Point an instance at a source tree plus an Assumptions-of-Use document. Ask it to cross-reference: which AOUs are implemented, which are missing, which are partially/incorrectly handled. Output a structured gap report.
34
+ - **UC-3: Code-quality agent.** An instance specialized for unit test generation and static-analysis triage against a Parasoft or SonarQube-based project, following project conventions it learned from the knowledge base.
35
+
36
+ ### Non-goals (v1)
37
+
38
+ - **No model fine-tuning / weight training in v1.** "Training" an instance means RAG (retrieval-augmented generation) over its knowledge base plus persistent memory — not gradient updates. Fine-tuning (LoRA adapters via e.g. `unsloth` or `axolotl`) is a v2+ phase; the `Instance` abstraction must not preclude it, but do not build it now.
39
+ - **No multi-user auth/tenancy in v1.** Single user, local trust boundary. Cloud deploys get a single API token.
40
+ - **No support for closed-source cloud LLM APIs as the primary path.** An OpenAI-compatible provider adapter exists for flexibility/testing, but the design center is local + self-hosted.
41
+
42
+ ---
43
+
44
+ ## 2. Core concepts
45
+
46
+ | Concept | Definition |
47
+ |---|---|
48
+ | **Provider** | A backend that serves model inference. v1: Ollama (primary), llama.cpp server, vLLM, generic OpenAI-compatible endpoint. Swappable via YAML. |
49
+ | **Model** | A concrete model identifier on a provider (e.g., `qwen2.5:32b-instruct` on Ollama). |
50
+ | **Instance** | The central abstraction. A named, persistent specialization = model + knowledge base(s) + memory store + behavior config (system prompt, accuracy gate, citation policy). Instances are what users chat with. |
51
+ | **Knowledge Base (KB)** | An ingested, chunked, embedded document/code corpus. KBs are independent objects; an instance can mount one or more. Re-ingestable and versioned. |
52
+ | **Memory** | Per-instance persistent state across sessions: conversation summaries, user-stated facts/preferences, and instance-learned notes. Stored locally, fully inspectable, deletable on command. |
53
+ | **Accuracy Gate** | A configurable grounding/confidence threshold governing when the instance answers vs. refuses/hedges. See §4.3 — this is *not* a magic accuracy dial; the semantics matter. |
54
+ | **Session** | One conversation thread with an instance. Sessions feed memory (via summarization) but are distinct from it. |
55
+
56
+ ---
57
+
58
+ ## 3. Architecture
59
+
60
+ ```
61
+ ┌───────────────────────────────────────────────┐
62
+ │ Clients │
63
+ │ sage CLI (Typer) Web UI (React, later) │
64
+ └──────────────┬────────────────┬───────────────┘
65
+ │ (same API) │
66
+ ┌──────▼────────────────▼──────┐
67
+ │ sage-server │
68
+ │ FastAPI (REST + SSE) │
69
+ ├──────────────────────────────┤
70
+ │ Instance Manager │
71
+ │ Session Manager │
72
+ │ Accuracy Gate / Verifier │
73
+ │ Citation Assembler │
74
+ ├──────────┬──────────┬────────┤
75
+ │ RAG │ Memory │Provider│
76
+ │ Pipeline │ Layer │Adapters│
77
+ └────┬─────┴────┬─────┴───┬────┘
78
+ │ │ │
79
+ ┌───────▼───┐ ┌────▼────┐ ┌──▼──────────────┐
80
+ │ Vector DB │ │ SQLite │ │ Ollama / vLLM / │
81
+ │ (Chroma / │ │ (memory,│ │ llama.cpp / │
82
+ │ Qdrant) │ │ configs,│ │ OpenAI-compat │
83
+ └───────────┘ │ sessions│ └─────────────────┘
84
+ └─────────┘
85
+ ```
86
+
87
+ **Key structural decision: the CLI is a thin client of the local server.** The CLI does not implement chat/RAG logic itself; it talks to `sage-server` over HTTP (auto-starting it if needed, like `ollama` does). This guarantees CLI/web feature parity by construction — both are clients of one API — and makes the cloud story trivial: a cloud deploy is the same server on a remote box.
88
+
89
+ ### 3.1 Tech stack
90
+
91
+ | Layer | Choice | Rationale |
92
+ |---|---|---|
93
+ | Language | Python 3.11+ | Ecosystem for RAG/embeddings; user preference for tooling. |
94
+ | CLI | Typer + Rich | Modern, typed, good help text, pretty terminal output. |
95
+ | Server | FastAPI + Uvicorn | Async, SSE streaming, OpenAPI schema for free (web UI consumes it). |
96
+ | Config | YAML + Pydantic v2 models | Every config file validates against a Pydantic schema; schema errors are user-facing and precise. |
97
+ | Vector store | ChromaDB (embedded) default; Qdrant adapter for scale/cloud | Chroma = zero-ops local. Qdrant when a KB outgrows it or for cloud. Behind a `VectorStore` interface. |
98
+ | Relational store | SQLite (WAL mode) | Instances, sessions, memory, KB manifests. Single file per Sage home dir; trivial backup. |
99
+ | Embeddings | Local via Ollama (`nomic-embed-text` / `bge-m3`) or `sentence-transformers` | No data leaves the machine. Pluggable. |
100
+ | Inference providers | Ollama (primary), llama.cpp server, vLLM, OpenAI-compatible | Behind a `Provider` interface; see §4.4. |
101
+ | Doc parsing | PyMuPDF (PDF), `unstructured` fallback, tree-sitter (code), plain parsers (md/txt/rst) | Datasheets are PDF-heavy; code KBs need syntax-aware chunking. |
102
+ | Web UI (Phase 5) | React + Vite, consuming the FastAPI OpenAPI schema | Deferred until CLI proves the workflows. |
103
+ | Packaging | `uv` + `pyproject.toml`; single `pipx install sage` target | One-command install. |
104
+ | Cloud deploy | Dockerfile + compose; `sage deploy` wraps provisioning for a v1 target (Fly.io or a bare VPS via SSH) | One command, but honest: v1 supports ONE cloud target well rather than five badly. |
105
+
106
+ ### 3.2 Repository layout
107
+
108
+ ```
109
+ sage/
110
+ ├── CLAUDE.md # this file
111
+ ├── pyproject.toml
112
+ ├── docs/
113
+ │ ├── design/ # ADRs — one per significant decision
114
+ │ └── lessons.md # running lessons log (see §7)
115
+ ├── src/sage/
116
+ │ ├── cli/ # Typer app; thin HTTP client of the server
117
+ │ ├── server/
118
+ │ │ ├── api/ # FastAPI routers (instances, kb, chat, memory, config)
119
+ │ │ ├── core/ # instance manager, session manager
120
+ │ │ ├── rag/ # ingestion, chunking, embedding, retrieval, reranking
121
+ │ │ ├── verify/ # accuracy gate: grounding checks, self-consistency, citation assembly
122
+ │ │ ├── memory/ # memory store, summarizer, deletion
123
+ │ │ └── providers/ # ollama.py, llamacpp.py, vllm.py, openai_compat.py + base.py
124
+ │ ├── config/ # Pydantic schemas for all YAML files
125
+ │ └── store/ # SQLite + vector store interfaces and impls
126
+ ├── tests/
127
+ │ ├── unit/
128
+ │ ├── integration/ # against a real local Ollama (marked, skippable in CI)
129
+ │ └── fixtures/ # sample PDFs, code trees, AOU docs for UC-1/2/3
130
+ └── deploy/
131
+ ├── Dockerfile
132
+ └── compose.yaml
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 4. Design decisions and rationale
138
+
139
+ ### 4.1 RAG-first, not fine-tuning-first
140
+
141
+ "Feed it a datasheet and it becomes an expert" is achieved in v1 by high-quality RAG: parsing → structure-aware chunking → embedding → hybrid retrieval (dense + BM25) → optional reranking → grounded generation with citations. This is the right call because:
142
+
143
+ - Fine-tuning on a datasheet teaches *style*, not reliable *facts*; RAG with citations gives verifiable answers, which is the whole point of the accuracy requirement.
144
+ - KB updates (datasheet rev 2.1 → 2.2) become a re-ingest, not a re-train.
145
+ - It keeps v1 shippable on a single workstation GPU/CPU.
146
+
147
+ The `Instance` schema reserves an `adapters:` field (unused in v1) so LoRA support can arrive later without a breaking change.
148
+
149
+ ### 4.2 Knowledge bases are versioned and swappable
150
+
151
+ Each ingest produces an immutable KB **snapshot** with a content hash and manifest (source files, parser versions, chunking params, embedding model). An instance mounts a KB by name at a pinned snapshot or `latest`. This gives:
152
+
153
+ - Clean rollback when a new datasheet rev has errors.
154
+ - Reproducibility: an answer can be traced to KB snapshot + chunk IDs.
155
+ - Instance history survives KB swaps — memory and sessions live separately from KBs.
156
+
157
+ ### 4.3 The Accuracy Gate — honest semantics
158
+
159
+ **Be direct about this in the implementation and the docs: no system can guarantee "≥85% accurate responses."** Accuracy is only measurable against ground truth after the fact. What we *can* build — and what the user's requirement actually operationalizes to — is a **grounding and confidence gate**:
160
+
161
+ 1. **Grounding check (primary).** After generation, verify each factual claim in the draft answer is supported by retrieved chunks. Implementation: a second lightweight verification pass ("judge" call to the same or a smaller model) scoring claim⇄chunk support. Compute a grounding score ∈ [0,1].
162
+ 2. **Retrieval confidence.** If top-k retrieval similarity scores are weak (below a floor), the KB likely doesn't contain the answer → refuse early with "not in my knowledge base," *before* generation.
163
+ 3. **Self-consistency (optional, expensive, off by default).** Sample N=3 answers at temperature; measure agreement.
164
+
165
+ The YAML knob:
166
+
167
+ ```yaml
168
+ accuracy:
169
+ min_grounding_score: 0.85 # gate on the grounding check
170
+ on_below_threshold: refuse # refuse | hedge | answer_with_warning
171
+ require_citations: true # every factual claim must carry chunk citations
172
+ retrieval_floor: 0.35 # refuse pre-generation if retrieval is this weak
173
+ self_consistency:
174
+ enabled: false
175
+ samples: 3
176
+ ```
177
+
178
+ `refuse` responses must say *why* (weak retrieval vs. failed grounding) and show the nearest chunks found, so the user can judge whether the KB is missing material or the question is out of scope. This is the anti-hallucination ("ghosting") mechanism: the instance is structurally prevented from confidently asserting unsupported claims when the gate is on.
179
+
180
+ ### 4.4 Provider abstraction
181
+
182
+ ```python
183
+ class Provider(ABC):
184
+ async def list_models(self) -> list[ModelInfo]: ...
185
+ async def pull(self, model: str) -> AsyncIterator[PullProgress]: ...
186
+ async def chat(self, req: ChatRequest) -> AsyncIterator[ChatChunk]: ... # streaming
187
+ async def embed(self, texts: list[str], model: str) -> list[list[float]]: ...
188
+ async def health(self) -> ProviderHealth: ...
189
+ ```
190
+
191
+ All four v1 adapters (Ollama, llama.cpp, vLLM, OpenAI-compatible) implement this. Instance YAML selects provider + model; switching is a config edit, nothing else. The OpenAI-compatible adapter doubles as the escape hatch for any hosted endpoint the user chooses to trust.
192
+
193
+ **Recommended default models (document in README, don't hardcode):**
194
+
195
+ - General instruct, 16–24 GB VRAM class: `qwen2.5:32b-instruct` (or 14b for smaller GPUs)
196
+ - Code-heavy instances (UC-2, UC-3): `qwen2.5-coder:32b`
197
+ - Small/fast judge model for grounding checks: `qwen2.5:7b` or `llama3.1:8b`
198
+ - Embeddings: `nomic-embed-text` (fast) or `bge-m3` (better multilingual/long-doc)
199
+
200
+ These are defaults, not requirements — the whole point is that they're YAML.
201
+
202
+ ### 4.5 Memory design
203
+
204
+ Per-instance memory, three tiers, all in SQLite, all inspectable:
205
+
206
+ 1. **Session transcripts** — raw, per session.
207
+ 2. **Rolling summaries** — each session is summarized on close (or every N turns) into compact memory entries; these are injected into future system prompts within a token budget.
208
+ 3. **Pinned facts** — user-stated ("remember that our target is SA8797 rev B") or explicitly promoted from summaries.
209
+
210
+ Commands / API:
211
+
212
+ ```
213
+ sage memory show <instance> # full, human-readable dump
214
+ sage memory pin <instance> "fact..."
215
+ sage memory forget <instance> --entry <id>
216
+ sage memory wipe <instance> [--sessions] [--all] # explicit, confirmed deletion
217
+ ```
218
+
219
+ `wipe --all` deletes memory rows *and* runs `VACUUM` so the data is actually gone from the SQLite file, not just unlinked. Memory sync for cloud instances is out of scope for v1 (cloud instance has its own memory on its own volume); a export/import command (`sage memory export/import`) covers migration.
220
+
221
+ ### 4.6 Config: YAML everywhere, one schema
222
+
223
+ Global config: `~/.sage/config.yaml`. Per-instance config: `~/.sage/instances/<name>/instance.yaml`. Every file validates against Pydantic schemas in `src/sage/config/`; the web UI edits the same objects via the API. Example instance file:
224
+
225
+ ```yaml
226
+ name: sa8775-expert
227
+ provider: ollama
228
+ model: qwen2.5:32b-instruct
229
+ system_prompt: |
230
+ You are an expert on the Qualcomm SA8775 platform. Answer strictly from
231
+ the mounted knowledge base. Cite sections for every factual claim.
232
+ knowledge_bases:
233
+ - name: sa8775-docs
234
+ snapshot: latest
235
+ accuracy:
236
+ min_grounding_score: 0.85
237
+ on_below_threshold: refuse
238
+ require_citations: true
239
+ memory:
240
+ enabled: true
241
+ summary_every_n_turns: 12
242
+ max_memory_tokens: 2000
243
+ generation:
244
+ temperature: 0.2
245
+ max_tokens: 2048
246
+ ```
247
+
248
+ ### 4.7 CLI surface (v1 target)
249
+
250
+ ```
251
+ sage init # create ~/.sage, global config
252
+ sage up | down | status # local server lifecycle
253
+ sage models list | pull <model>
254
+ sage instance create <name> [--from instance.yaml]
255
+ sage instance list | show | edit | delete <name>
256
+ sage kb create <name> --source <path> [--source ...] # ingest files/dirs
257
+ sage kb update <name> | list | show <name> | snapshots <name>
258
+ sage chat <instance> # interactive REPL, streaming, citations rendered
259
+ sage ask <instance> "question" [--json] # one-shot, scriptable
260
+ sage memory show|pin|forget|wipe <instance>
261
+ sage analyze aou --code <dir> --aou <file> --instance <name> # UC-2 workflow command
262
+ sage deploy --target <name> | sage deploy status|destroy # Phase 4
263
+ sage doctor # env/provider/GPU diagnostics
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 5. Phased implementation plan
269
+
270
+ Rules: every PR is small, independently mergeable, and leaves `main` green. Each PR lists its own verification steps; **do not mark a PR done until its verification steps have actually been run and pass.** Integration tests that need a live Ollama are marked `@pytest.mark.requires_ollama` and skipped in CI, but must be run locally before merging PRs that touch inference paths.
271
+
272
+ ### Phase 0 — Skeleton & foundations (PRs 1–4)
273
+
274
+ - **PR-1**: Repo scaffold. `pyproject.toml` (uv), `src/sage` package layout, ruff + mypy + pytest config, pre-commit, CI workflow (lint + unit tests), empty Typer app (`sage --version`), `docs/lessons.md`. *Verify: `uv run sage --version`; CI green.*
275
+ - **PR-2**: Config schemas. Pydantic models for global + instance + KB YAML; loader with precise validation errors; `sage init`. *Verify: unit tests for valid/invalid YAML round-trips.*
276
+ - **PR-3**: SQLite store. Schema (instances, kbs, kb_snapshots, sessions, messages, memory_entries), migrations (simple versioned SQL), WAL mode, repository layer. *Verify: unit tests incl. migration from empty DB.*
277
+ - **PR-4**: FastAPI server skeleton + lifecycle. `sage up/down/status`, health endpoint, CLI HTTP client base with auto-start. *Verify: `sage up && sage status && sage down`.*
278
+
279
+ ### Phase 1 — Providers & basic chat (PRs 5–8)
280
+
281
+ - **PR-5**: `Provider` interface + Ollama adapter (chat streaming, embed, pull, health). *Verify: integration test against local Ollama.*
282
+ - **PR-6**: Instance CRUD (API + CLI). `instance create/list/show/edit/delete`, instance.yaml materialization. *Verify: unit + CLI e2e tests.*
283
+ - **PR-7**: Sessions + basic chat (no RAG yet). `sage chat` REPL with streaming via SSE, `sage ask`, transcripts persisted. *Verify: live chat against Ollama; transcript rows present.*
284
+ - **PR-8**: OpenAI-compatible adapter + provider selection in instance YAML. *Verify: adapter integration test against Ollama's OpenAI-compat endpoint (no external network needed).*
285
+
286
+ ### Phase 2 — RAG & knowledge bases (PRs 9–13)
287
+
288
+ - **PR-9**: Ingestion pipeline core. Source walker, file-type routing, PDF (PyMuPDF) + text/markdown parsers, structure-aware chunking with metadata (page, heading path). *Verify: fixture datasheet PDF chunks with correct page metadata.*
289
+ - **PR-10**: Embedding + vector store. `VectorStore` interface, Chroma impl, embed-on-ingest, KB snapshots with content-hash manifests. *Verify: ingest fixture KB; re-ingest unchanged → same snapshot hash.*
290
+ - **PR-11**: Retrieval. Hybrid dense + BM25, top-k merge, retrieval-floor check. *Verify: recall test on fixture Q/A pairs.*
291
+ - **PR-12**: Grounded chat. Retrieval → prompt assembly → generation → citation assembly (chunk IDs → page/section refs rendered in CLI). Instance mounts KBs. *Verify: UC-1 smoke — ask 10 fixture questions, all answers carry correct citations.*
292
+ - **PR-13**: Code ingestion. tree-sitter chunking for C/C++/Python, file-path metadata. *Verify: ingest a fixture C project; retrieval returns function-level chunks.*
293
+
294
+ ### Phase 3 — Accuracy gate & memory (PRs 14–17)
295
+
296
+ - **PR-14**: Grounding verifier. Claim extraction + judge pass + grounding score; gate actions (refuse/hedge/warn) per YAML; refusal messages include nearest chunks + reason. *Verify: adversarial fixture questions (answers NOT in KB) get refused; in-KB questions pass.*
297
+ - **PR-15**: Memory layer. Session summarization, pinned facts, token-budgeted injection, `sage memory show/pin/forget/wipe` (with VACUUM on wipe). *Verify: fact stated in session 1 is recalled in session 2; wiped memory is unrecoverable.*
298
+ - **PR-16**: `sage analyze aou` (UC-2). Structured workflow: enumerate AOU items → per-item retrieval against code KB → implemented/partial/missing classification with cited evidence → markdown/JSON report. *Verify: fixture code tree + AOU doc produces correct gap report.*
299
+ - **PR-17**: Self-consistency option + `sage doctor`. *Verify: doctor detects missing Ollama, missing models, GPU presence.*
300
+
301
+ ### Phase 4 — Deploy (PRs 18–19)
302
+
303
+ - **PR-18**: Dockerfile + compose (server + Ollama + volume layout); `sage deploy` for local Docker. *Verify: `sage deploy --target docker` yields a working remote-style instance; memory persists across container restarts.*
304
+ - **PR-19**: One real cloud target (pick: Fly.io GPU or SSH-to-VPS provisioner). `sage deploy --target <name>`, `deploy status/destroy`, token auth on the server, `memory export/import`. *Verify: end-to-end deploy, chat, destroy.*
305
+
306
+ ### Phase 5 — Web UI (PRs 20+)
307
+
308
+ - **PR-20**: React scaffold consuming the OpenAPI schema; chat view with streaming + citation rendering.
309
+ - **PR-21+**: Instance management UI, KB upload/ingest UI, memory browser, config editor (schema-driven forms from Pydantic → JSON Schema). Feature parity is cheap because everything is already API-first.
310
+
311
+ ---
312
+
313
+ ## 6. Testing & verification requirements
314
+
315
+ - Unit tests for every module; no PR merges without tests for its new logic.
316
+ - Fixture corpus in `tests/fixtures/`: a small public processor datasheet (or synthetic equivalent), a toy C project + hand-written AOU doc with known implemented/missing items, and Q/A ground-truth pairs for retrieval/grounding evals.
317
+ - **Grounding eval harness** (built in PR-14, extended after): a scored set of in-KB and out-of-KB questions; track grounding-gate precision/recall across changes. This is how "accuracy" claims stay honest — measured, not asserted.
318
+ - Sanitizer-grade discipline applies conceptually: run integration tests before claiming inference-path PRs done; never claim "verified" for anything not actually executed.
319
+
320
+ ## 7. Engineering conventions
321
+
322
+ - Small, independently mergeable PRs per the plan above; each PR description lists verification steps actually performed.
323
+ - Architecture changes get a short ADR in `docs/design/` before implementation.
324
+ - Maintain `docs/lessons.md`: every non-obvious bug or wrong assumption gets an entry.
325
+ - Python style: ruff + mypy strict on `src/`; type hints everywhere; async throughout the server.
326
+ - Honest limitations stay documented: the accuracy gate reduces hallucination and gates on grounding — it does not and cannot guarantee a numeric accuracy percentage. Never let README copy claim otherwise.
327
+
328
+ ## 8. Open questions (decide before or during the relevant PR)
329
+
330
+ 1. Judge model for grounding: same model as generator (simpler) vs. dedicated small model (faster, cheaper)? Default: small dedicated model, configurable. (PR-14)
331
+ 2. Reranker (e.g., `bge-reranker-v2-m3`) in the retrieval path: measurable win on datasheets, adds latency. Decide with eval data. (PR-11/12)
332
+ 3. Cloud target for PR-19: Fly.io GPU vs. plain VPS+SSH. VPS is more universal; Fly is less work.
333
+ 4. Qdrant adapter timing: only when a real KB outgrows Chroma — don't build speculatively.