Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
0235ad8
feat: integrate kreuzberg 5.x
tobocop2 Jun 24, 2026
a5bee1e
feat: support all kreuzberg formats + configurable OCR language
tobocop2 Jun 25, 2026
7dd5df6
feat: migrate kreuzberg -> xberg 1.0.0rc1
tobocop2 Jun 25, 2026
1cc9a00
feat(wiki): adopt xberg footnote/citation API, gut hand-rolled verifi…
tobocop2 Jun 25, 2026
cef45cc
feat: route semantic-chunking embeddings through lilbee's fleet (bb-5…
tobocop2 Jun 25, 2026
719c705
docs: surface multi-language OCR (LILBEE_OCR_LANGUAGE)
tobocop2 Jun 25, 2026
9166f51
fix(ocr): round-trip ocr_language through the persist path; document …
tobocop2 Jun 25, 2026
0cac971
chore(lint): clear pre-existing lint gate flags
tobocop2 Jun 25, 2026
49e9910
refactor(chunk): route semantic-chunk embeddings through lilbee's plu…
tobocop2 Jun 25, 2026
d646561
build: integrate xberg all-PRs wheel; drop plugin type-ignore
tobocop2 Jun 26, 2026
1f57a18
Merge remote-tracking branch 'origin/feat/local-model-api' into feat/…
tobocop2 Jun 26, 2026
abb3d1d
test(placement): cover the placement-screen error/guard branches
tobocop2 Jun 26, 2026
7aa9a9c
fix(ocr): read the native OcrConfig in the vision backend callback
tobocop2 Jun 26, 2026
563d299
license: relicense from Elastic License 2.0 to MIT
tobocop2 Jun 27, 2026
13e6bd9
style: reformat a merged placement test to the locked ruff
tobocop2 Jun 27, 2026
229b663
wip(xberg): migrate to the new async extract()/ExtractedDocument API
tobocop2 Jun 29, 2026
260f754
Merge remote-tracking branch 'origin/feat/local-model-api' into feat/…
tobocop2 Jun 29, 2026
696021b
fix(deps): update xberg wheel hash to match rebuilt rc1 wheel
tobocop2 Jun 29, 2026
fea490c
fix(xberg): finish new-API migration — test mocks, OCR return type, o…
tobocop2 Jun 29, 2026
68cfcf7
test(xberg): cover the async-extract bridge error + offload branches
tobocop2 Jun 29, 2026
6376e61
build(xberg): integrate 1.0.0rc2 cp310-abi3 wheel
tobocop2 Jul 1, 2026
0e6c959
fix(config): honor LILBEE_SKIP_TOML_CONFIG on the overlay path; make …
tobocop2 Jul 1, 2026
045c6b4
test: reset xberg_extract module globals between tests to kill a cros…
tobocop2 Jul 2, 2026
5e4beb4
Merge remote-tracking branch 'origin/feat/local-model-api' into feat/…
tobocop2 Jul 2, 2026
0daa560
Merge origin/feat/local-model-api into feat/kreuzberg-5
tobocop2 Jul 3, 2026
5b63317
Hoist unnecessary lazy cfg imports to module scope (#475)
tobocop2 Jul 3, 2026
c8f3083
build(xberg): integrate 1.0.0rc5 cp310-abi3 wheel
tobocop2 Jul 3, 2026
1485dde
build: add Intel-Mac (macos-15-intel) standalone build cells
tobocop2 Jul 3, 2026
371908e
Drop xberg type:ignores: rc5 stub fix + full OcrBackend conformance
tobocop2 Jul 3, 2026
6d0f9d4
Merge origin/feat/local-model-api into feat/kreuzberg-5
tobocop2 Jul 3, 2026
5cbf7d9
build(xberg): relock rc5 wheel hash after rebuild
tobocop2 Jul 4, 2026
b8bda2c
build(xberg): integrate wheel built from main (rc5 + 3 commits)
tobocop2 Jul 4, 2026
596d5b7
test(integration): migrate PDF OCR test off the removed extract_file API
tobocop2 Jul 4, 2026
f616df0
build(xberg): integrate 1.0.0rc6 (bindings regenerated off alef main)
tobocop2 Jul 4, 2026
4e8466b
ingest: per-file extraction trace logging for diagnostics
tobocop2 Jul 4, 2026
a95a325
fix(ingest): resolve the OCR request token from JSON-string backend_o…
tobocop2 Jul 4, 2026
62a9b6a
fix(ingest): embed ctx = the chunker's character budget, the provable…
tobocop2 Jul 4, 2026
c22d96e
fix: resolve five local-model-api bugs (#482)
tobocop2 Jul 4, 2026
2f062d0
fix(chat): keep the reasoning-exhausted notice out of memory extraction
tobocop2 Jul 4, 2026
b188daa
ingest: LILBEE_OCR_FORCE env lever for garbage-text-layer scans
tobocop2 Jul 5, 2026
577430a
Manual placement derives planner-style tensor splits instead of even …
tobocop2 Jul 5, 2026
6c7a608
Merge pull request #479 from tobocop2/chore/intel-mac-cells
tobocop2 Jul 5, 2026
ed3fe54
build(xberg): integrate 1.0.0rc7 (canonical kreuzberg main)
tobocop2 Jul 4, 2026
3e90f61
build(xberg): refresh 1.0.0rc7 wheel with tokenizer-backend registry
tobocop2 Jul 5, 2026
0e5feff
build(xberg): integrate 1.0.0rc9 with tokenizer-backed chunk sizing
tobocop2 Jul 5, 2026
c825bc3
feat(chunk): size chunks with the embedder's own tokenizer
tobocop2 Jul 5, 2026
49dee2e
fix: reconcile the local-model-api ingest/registry commits with rc9
tobocop2 Jul 5, 2026
ffc95b3
merge: main into feat/kreuzberg-5
tobocop2 Jul 6, 2026
a653c75
merge: latest main into feat/kreuzberg-5
tobocop2 Jul 7, 2026
d52e631
fix(ingest): scale the extraction offload pool with vCPU count
tobocop2 Jul 8, 2026
bd0f720
feat(ingest): adaptive concurrency that hill-climbs to the hardware's…
tobocop2 Jul 8, 2026
ca1d574
fix(ingest): surface silently dropped document files after a sync
tobocop2 Jul 8, 2026
2605a25
fix(ingest): cap the extraction pool default at 32 (OCR ingest is GPU…
tobocop2 Jul 9, 2026
0d2d270
style: format test_ingest.py
tobocop2 Jul 11, 2026
99092c1
chore: step up to xberg 1.0.0rc22 from PyPI
tobocop2 Jul 11, 2026
29b16f4
merge: latest main into feat/kreuzberg-5
tobocop2 Jul 11, 2026
7401868
Drop the accidentally-committed WIP progress test again
tobocop2 Jul 11, 2026
68e7331
fix(services): serialize singleton creation and xberg backend re-binds
tobocop2 Jul 11, 2026
7084ea4
build: make the macOS floor of each binary honest and enforce it
tobocop2 Jul 12, 2026
d03245d
Merge remote-tracking branch 'origin/main' into feat/kreuzberg-5
tobocop2 Jul 12, 2026
d77ae88
retrieval: canonical score fusion and intent routing
tobocop2 Jul 10, 2026
bb2347b
retrieval: resolve document references by content when filenames don'…
tobocop2 Jul 10, 2026
b59482b
retrieval: budget context against the engine's served window, not the…
tobocop2 Jul 10, 2026
e2722f8
retrieval: opt-in typed entity extraction with schema induction and e…
tobocop2 Jul 10, 2026
f3a1daa
review: dedupe the induction sample constant, document the entities C…
tobocop2 Jul 10, 2026
835d45f
entities: match anchored patterns per token; disclose proxy counts
tobocop2 Jul 11, 2026
dc444ee
entities: backfill --replace clears prior rows first
tobocop2 Jul 11, 2026
d57fe98
searcher: log expansion skips and variant counts at info
tobocop2 Jul 11, 2026
f370db7
fix(retrieval): return hybrid fusion to reciprocal rank; no MMR on th…
tobocop2 Jul 11, 2026
bcd1306
docs: align remaining fusion wording with reciprocal rank
tobocop2 Jul 11, 2026
06aabc1
fix(retrieval): retrieve rerank_candidates deep when a reranker is co…
tobocop2 Jul 11, 2026
9d4623d
fix(retrieval): resolve known-item queries on bare search, not just ask
tobocop2 Jul 11, 2026
3217a9c
fix(tests): stop a setup-wizard test starting a real model download
tobocop2 Jul 11, 2026
8b19c55
feat(retrieval): rerank_blend toggle for pure cross-encoder ordering
tobocop2 Jul 11, 2026
cb58b59
fix(tui): guard catalog refreshes against unmounted containers; wait …
tobocop2 Jul 12, 2026
aa38e8a
feat(entities): automatic sync-time lifecycle replaces the entities c…
tobocop2 Jul 12, 2026
3e319fb
retrieval: harden quoted-name detection and share noun pluralization
tobocop2 Jul 13, 2026
5fab95f
ask: an empty library answers with add-content guidance, not a failed…
tobocop2 Jul 13, 2026
fab8d26
entities: read the lifecycle's config through the active scope
tobocop2 Jul 13, 2026
8af27f5
tests: pin library_empty in the litestar stream-mismatch helper
tobocop2 Jul 13, 2026
109b5ff
Force OCR every page via ExtractionConfig.force_ocr, not impossible q…
tobocop2 Jul 13, 2026
4daf0dc
entities: persist the schema inside the index, not as a file
tobocop2 Jul 13, 2026
60c7a2f
Merge remote-tracking branch 'origin/main' into fix/hybrid-ann-recall
tobocop2 Jul 13, 2026
eb40124
Merge remote-tracking branch 'origin/main' into fix/hybrid-ann-recall
tobocop2 Jul 13, 2026
354a169
entities: keep the induced taxonomy up to date as the corpus grows
tobocop2 Jul 13, 2026
3a3df8e
Merge remote-tracking branch 'origin/main' into fix/hybrid-ann-recall
tobocop2 Jul 13, 2026
241b53f
server: reuse the mismatch-detail helper in the shared stream resolver
tobocop2 Jul 13, 2026
f0ab458
Drop a stray blank line the merge left in the chat screen
tobocop2 Jul 13, 2026
a02fb97
tests: crawl integration test joins the worker it starts
tobocop2 Jul 13, 2026
a2c060e
Merge remote-tracking branch 'origin/fix/hybrid-ann-recall' into k5-m…
tobocop2 Jul 13, 2026
b4d0d21
Index table structure and layout-ordered text from xberg extraction (…
tobocop2 Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 72 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,36 @@ jobs:
compat: true
soft: true
march: x86-64-v2
# macos_floor is the OS this asset actually runs on, and every bundled
# dylib is checked against it. `uv sync` resolves wheels for the runner,
# so numpy/scipy arrive as macosx_14_0 and 14.0 is the honest floor here.
- os: macos-latest
asset_name: lilbee-macos-arm64
backend: metal
# Intel Mac dropped: macos-13 hosted runners stay queued; pip wheel covers them.
macos_floor: "14.0"
# Intel Mac: built natively on macos-15-intel, GitHub's Intel image
# (Nuitka can't cross-compile arch from the arm64 runner). Soft because
# the Intel image can queue longer than the arm64 pool -- this asset must
# never block the release fan-out. The install below pins wheel resolution
# to the x86_64 target rather than the runner, so the deps come in at their
# oldest macOS build (pyarrow's 12.0 is the binding one) and the AVX
# baseline reaches every Ivy Bridge / Haswell Mac, which cap at macOS 12.
- os: macos-15-intel
asset_name: lilbee-macos-x86_64
backend: cpu
soft: true
macos_floor: "12.0"
# Pre-Haswell-safe Intel Mac build: same self-contained model as the
# linux/windows compat cells (stock lancedb swapped for +compat, launcher
# held to x86-64-v2). Covers the AVX-but-no-AVX2 tail (e.g. 2013 Mac Pro)
# and guards against an AVX2-baselined stock lancedb SIGILL.
- os: macos-15-intel
asset_name: lilbee-compat-macos-x86_64
backend: cpu
compat: true
soft: true
march: x86-64-v2
macos_floor: "12.0"
- os: windows-latest
asset_name: lilbee-windows-x86_64.exe
backend: vulkan
Expand Down Expand Up @@ -218,13 +244,51 @@ jobs:
save: ${{ github.ref_type != 'tag' }}

- name: Install dependencies
if: matrix.os != 'macos-15-intel'
run: uv sync --extra release && uv pip install 'nuitka[onefile]>=4.0,<5' pip

# Intel Mac: `uv sync` can't run here -- stock lancedb 0.33.0 ships no
# macosx_x86_64 wheel. Resolve it from the +compat index (PEP 440 drop-in),
# then install the rest of [release] fresh from PyPI. CMAKE_ARGS + deployment
# target hold any throwaway llama-cpp-python sdist build at the cpu/x86_64
# baseline; the dedicated step below builds the llama-cpp-python that actually
# ships. The cell stays soft so an install failure never blocks the fan-out.
#
# --python-platform resolves wheels for the x86_64 target instead of the
# runner. Without it uv picks the newest wheel the macOS 15 image accepts
# (numpy/scipy macosx_14_0) and bundles a binary that dies on macOS 12-13,
# even though both ship older-built wheels that run just as well here.
- name: Install dependencies (Intel Mac)
if: matrix.os == 'macos-15-intel'
shell: bash
env:
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos_floor }}
run: |
set -euxo pipefail
eval "$(BACKEND=cpu TARGET_ARCH=x86_64 tools/wheel-build/cmake_args.sh)"
export CMAKE_ARGS
uv venv
uv pip install --python-platform x86_64-apple-darwin \
--extra-index-url https://lilbee.sh/compat/ \
lancedb==0.33.0+compat
uv pip install --python-platform x86_64-apple-darwin \
'.[release]' 'nuitka[onefile]>=4.0,<5' pip

- name: Verify extras installed in build venv
shell: bash
# --no-sync is load-bearing: re-syncing reinstalls python-multipart and shadows the shim.
run: uv run --no-sync python -c "import crawl4ai, litellm, spacy, graspologic_native; print('extras OK')"

# Nuitka bundles these dylibs, and dyld enforces each one's minos separately
# from the launcher's, so one dep built on a newer runner silently ships a
# binary that dies at load. Fail here rather than on the user's Mac.
- name: Verify macOS floor
if: runner.os == 'macOS'
shell: bash
env:
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos_floor }}
run: bash tools/wheel-build/check_macos_floor.sh

# compat cell only: swap the synced stock lancedb for the pre-Haswell build.
# --no-deps leaves the rest of the venv intact, so nothing leaves this cell.
# Pinned to the latest stable lancedb for Python (0.33), served by the fork as
Expand All @@ -248,13 +312,19 @@ jobs:
# engine cache separate from bare-runner builds.
cache-env: ${{ matrix.container || matrix.os }}
env:
MACOSX_DEPLOYMENT_TARGET: "11.0"
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos_floor }}

- name: Build executable
shell: bash
env:
ASSET_NAME: ${{ matrix.asset_name }}
PRODUCT_VERSION: ${{ env.LILBEE_NUITKA_VERSION }}
# Pin Nuitka's clang output to this asset's floor. Unset, clang defaults the
# deployment target to the build host (macOS 15), which would stamp the
# launcher minos=15 and refuse to launch below it. This only pins the
# launcher; the bundled dylibs carry their own minos, which is what the
# "Verify macOS floor" step above checks. No-op off macOS.
MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macos_floor }}
# compat cell only: hold Nuitka's C output to the pre-Haswell baseline so
# the launcher layer (not just lancedb) runs on old silicon. Empty elsewhere.
CFLAGS: ${{ matrix.march && format('-march={0}', matrix.march) || '' }}
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ CLAUDE.md
docs/plans/
demo.cast
docs/superpowers/
.kreuzberg/
.xberg/
.claude/
.dolt/
.doltcfg/
Expand Down
5 changes: 3 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ CLI also accepts `--model` / `-m` for chat model, `--data-dir` / `-d`, `--ocr-ti
- Type checking: `mypy` with strict settings
- **No em dashes (—)** — use periods or commas instead
- **No divider comments** (`# ------`) for grouping — use modules or classes instead
- **Don't scatter functions between constants.** Within a module (or a topical block in one), keep the module-level constants contiguous and the functions/classes contiguous. A `def` wedged into the middle of a run of `FOO = ...` / `BAR = ...` lines breaks the eye's scan. A helper that formats or validates a small group of constants may follow that group directly (the constants, then their one helper), but don't interleave: `A = ...`, `def f(): ...`, `B = ...`, `def g(): ...`, `C = ...` is wrong; group it as `A = ...`, `B = ...`, `C = ...`, `def f(): ...`, `def g(): ...`. Order by concern (all the "skip" constants, then the skip helpers; all the "doc table" constants, then the doc-table dataclass), not by when each was added.
- **Canonical module layout — group by kind, don't scatter.** A module reads top-to-bottom in this order: module docstring → `from __future__` → imports → module-level constants & data tables → enums → exception classes → data containers (`dataclass` / `TypedDict` / `NamedTuple` / `BaseModel`) → classes → functions. Within each band, order by concern, not by when each symbol was added. The reader should never have to scan past a `class` or `def` to find the next constant, nor past a constant to find the next class. A constant assignment (`FOO = ...`, `BAR: T = ...`) appearing AFTER the first `def`/`class` in a module is the scatter smell — hoist it into the constants band at the top. A `def` wedged into a run of `FOO = ...` / `BAR = ...` lines is the same smell from the other side. The one allowed exception: a single small helper that exists only to build or validate one adjacent group of constants may sit directly after that group (the constants, then their one helper) — but never interleaved (`A = ...`, `def f(): ...`, `B = ...` is always wrong). When in doubt, constants and data tables go at the top, contiguous; everything callable goes below, contiguous.
- **Annotate `isinstance` guards** with a comment explaining why the check is needed (e.g. untyped frontmatter, untyped SDK response). NEVER use `isinstance(self.app, LilbeeApp)` style host-narrowing in production: if a screen / widget needs LilbeeApp-specific access, declare `app: LilbeeApp # type: ignore[assignment]` at the class scope (mypy narrows; runtime is unchanged). Tests host via `tests/_lilbee_app_test_host.LilbeeAppHost` (a LilbeeApp subclass with `_test_skip_auto_init = True`), never `App[None]` / `class _PlainApp(App)`.
- Use literal unicode chars (`★`) not escapes (`\u2605`); extract to named constants
- **No filterwarnings** without explicit user approval; fix warnings at the source
Expand Down Expand Up @@ -190,7 +190,7 @@ Default: **every import lives at module top**, ordered stdlib, third-party, loca
**Permitted reasons for a function-local import:**

1. **Circular import.** Module A's top-level already imports module B, and module B needs a symbol from A. Put A's import of B inside the one function in B that needs it, with a comment `# circular: B -> A via <symbol>`.
2. **Heavy third-party lib.** Module-top import takes **>50 ms measured by `python -X importtime`** or loads native libraries. Known-heavy libs in this project: `litellm` (provider SDK fanout), `lancedb` (arrow + datafusion), `kreuzberg` (OCR stack), `gguf` (numpy), `sentence_transformers`, `spacy`, `crawl4ai`, `textual` when imported outside the TUI screen modules. (The local inference engine is the out-of-process `llama-server`, reached over HTTP, so it adds no heavy Python import.) Use importtime before declaring a lib heavy.
2. **Heavy third-party lib.** Module-top import takes **>50 ms measured by `python -X importtime`** or loads native libraries. Known-heavy libs in this project: `litellm` (provider SDK fanout), `lancedb` (arrow + datafusion), `xberg` (OCR stack), `gguf` (numpy), `sentence_transformers`, `spacy`, `crawl4ai`, `textual` when imported outside the TUI screen modules. (The local inference engine is the out-of-process `llama-server`, reached over HTTP, so it adds no heavy Python import.) Use importtime before declaring a lib heavy.
3. **CLI startup path.** The import lives inside a Typer command body so `lilbee --help` stays fast. Treat this as a sub-case of (2): the CLI loader stays lean so unused subcommands don't pay for their dependencies.

**Never lazy-import the following:**
Expand Down Expand Up @@ -390,6 +390,7 @@ work; the greps below remain the full set a reviewer still runs by eye.
- `grep -rn "App\[None\]\|class _PlainApp\b\|class _BareApp\b" tests/` — test host that bypasses LilbeeApp. Migrate to LilbeeAppHost.
- `grep -cE "isinstance\(.*widget, (Input|Checkbox|Select|TextArea)\)" src/` followed by "if more than one site" — type-keyed widget routing. Replace with a `dict[type, Callable]`.
- `grep -rnE "^\s*global \w+" src/` — module-level mutable globals. Encapsulate on a class.
- `for f in $(git diff --name-only main -- 'src/**/*.py'); do awk 'NR>1 && /^[A-Z_]+ *[:=]/ && seen {print FILENAME": "$0} /^(def |class )/{seen=1}' "$f"; done` — module-level constant assigned AFTER the first `def`/`class` (the scatter smell). Hoist it into the constants band at the top. See the canonical-module-layout rule in Code Style.
- `grep -rnE "def \w+\(.*\) -> .*:\s*$" src/ | xargs -I{} awk 'def_lines>20'` (proxy: scan modules >700 LOC) — functions over ~20 lines need a split into named sub-helpers.
- `grep -rn "from lilbee\.\(modelhub\|providers\|data\|retrieval\|wiki\|crawler\)" src/lilbee/catalog src/lilbee/core` — upward imports from foundation layers. catalog and core never import from anything above them.
- `grep -rn "from lilbee\.\(retrieval\|wiki\|crawler\|cli\|server\)" src/lilbee/data src/lilbee/modelhub src/lilbee/providers` — Layer-2 modules importing Layer-3+. Invert via callback or move the helper down.
Expand Down
Loading