Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## Unreleased

## 0.2.5860 - 2026-09-27

- `codedb semantic-index` sends larger batches with more requests in flight, so hosted index builds finish faster.
- Index builds wait out hosted rate limits with a longer capped backoff instead of failing after a few quick retries. Interactive `codedb context` calls still fail fast to local results.

## 0.2.5859 - 2026-09-27

- Fix `codedb context` reporting `semantic lane: unavailable` with the default hosted configuration. The hosted default model id is now `codedb-hosted-v1`. Local ANN sidecars built by earlier releases are rebuilt once in the background; custom endpoints and model overrides are unchanged.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,7 @@ endpoints/model overrides and new repositories are never auto-built; set
never mixes vectors from different models.

It splits already-indexable files into bounded 832-byte source chunks, uses
four concurrent 25-item requests by default (explicitly configurable from one
eight concurrent 100-item requests by default (explicitly configurable from one
to eight), and writes a
small `semantic-chunks-v3.meta` mapping plus a generation-named `.hmls` mmap
slab only in codedb's per-project local data directory. The directory and
Expand Down
2 changes: 1 addition & 1 deletion build.zig.zon
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.{
.name = .codedb2,
.fingerprint = 0x6e18d96ca2a31757,
.version = "0.2.5859",
.version = "0.2.5860",
.minimum_zig_version = "0.17.0-dev.813+2153f8143",
.dependencies = .{
.nanoregex = .{
Expand Down
4 changes: 2 additions & 2 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ command returns the local result and does not run an embedding model on CPU.
default; `--local` (or `--no-semantic`) is the explicit on-device-only mode.

`semantic-index` is the explicit ANN build trigger. It sends bounded 832-byte
code chunks using four concurrent 25-item requests by default (configurable
code chunks using eight concurrent 100-item requests by default (configurable
from one to eight) and stores a small mapping plus a generation-named,
validated `.hmls` mmap slab under codedb's local per-project data directory
(0700/0600 on POSIX).
Expand Down Expand Up @@ -161,7 +161,7 @@ codedb-cli stop # stop daemon
| `CODEDB_EMBEDDINGS_DIMENSIONS` | managed | Requested custom-provider dimensions (64-4096) |
| `CODEDB_EMBEDDINGS_TOKEN` | unset | Optional legacy bearer token for protected/custom endpoints; the hosted lane enrolls automatically |
| `CODEDB_EMBEDDINGS_TIMEOUT_MS` | `15000` | Per-request deadline in milliseconds (10-120000) |
| `CODEDB_SEMANTIC_INDEX_CONCURRENCY` | `4` | Parallel 25-item index batches (clamped to 1-8) |
| `CODEDB_SEMANTIC_INDEX_CONCURRENCY` | `8` | Parallel 100-item index batches (clamped to 1-8) |

## Performance

Expand Down
17 changes: 17 additions & 0 deletions docs/releases/0.2.5860.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# CodeDB 0.2.5860

Faster hosted semantic index builds.

## Hosted semantic retrieval

`codedb semantic-index` now sends 100-item batches with eight requests in flight by default. `CODEDB_SEMANTIC_INDEX_CONCURRENCY` still overrides the concurrency (1-8).

Index builds wait out hosted rate limits with a doubling backoff capped at 30 seconds, instead of failing after a few quick retries. Interactive `codedb context` calls are unchanged and still fall back to local results immediately.

Local retrieval, the watcher, and the installer are unchanged.

## Verification

```sh
zig build test --summary all
```
2 changes: 1 addition & 1 deletion npm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codedeebee",
"version": "0.2.5859",
"version": "0.2.5860",
"description": "Zig code intelligence MCP server — npx launcher for the codedb native binary",
"license": "MIT",
"author": "justrach",
Expand Down
2 changes: 1 addition & 1 deletion src/release_info.zig
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pub const semver = "0.2.5859";
pub const semver = "0.2.5860";
39 changes: 30 additions & 9 deletions src/semantic.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@ pub const default_timeout_ms: u32 = 15_000;
pub const min_timeout_ms: u32 = 10;
pub const max_timeout_ms: u32 = 120_000;
pub const max_documents: usize = 24;
// The free public codedb lane accepts at most 25 inputs per request. Indexing
// may fill all 25 slots because, unlike query reranking, it does not prepend a
// query vector to the batch.
pub const max_index_documents: usize = 25;
// The hosted lane accepts at most 100 inputs per request, the provider's own
// batch ceiling. Larger batches cut per-item latency and round trips; query
// reranking stays bounded separately by `max_documents`.
pub const max_index_documents: usize = 100;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound custom-provider batches by the response limit

When a custom OpenAI-compatible endpoint uses 3072 or 4096 dimensions and the repository supplies a full batch, this raises the response to 100 JSON-float vectors while max_response_bytes remains a fixed 2 MiB; ordinary decimal embeddings can therefore overflow the response writer and make semantic-index fail, whereas the previous 25-item batches fit. Keep smaller batches for custom endpoints or calculate a safe batch size from dimensions and the response cap.

Useful? React with 👍 / 👎.

pub const max_document_bytes: usize = 2 * 1024;
// Keep the serialized anonymous request below Caddy's 64 KiB raw-body cap even
// Keep the serialized request below the hosted edge's 256 KiB body cap even
// when source punctuation/control bytes expand during JSON escaping.
pub const max_batch_text_bytes: usize = 8 * 1024;
pub const max_request_bytes: usize = 60 * 1024;
pub const max_request_bytes: usize = 240 * 1024;
pub const max_response_bytes: usize = 2 * 1024 * 1024;
pub const max_index_batch_text_bytes: usize = 25 * 1024;
pub const max_index_embedding_attempts: usize = 3;
pub const max_index_batch_text_bytes: usize = 100 * 1024;
// Hosted quota windows are per minute. Ten attempts with doubling backoff
// capped at 30s wait about 92s in total, enough to outlast one window.
pub const max_index_embedding_attempts: usize = 10;
pub const index_embedding_retry_base_ms: u32 = 250;
pub const index_embedding_retry_max_ms: u32 = 30_000;
pub const default_rrf_k: f32 = 60;
pub const default_semantic_weight: f32 = 0.05;
// Accuracy-oriented Jina hybrid policy; see ADR 0008 for live-service
Expand Down Expand Up @@ -494,14 +497,32 @@ pub fn embedIndexRemoteTexts(
err == error.EmbeddingProviderUnavailable or
err == error.EmbeddingTimeout;
if (!retryable or attempt + 1 == max_index_embedding_attempts) return err;
const delay_ms = index_embedding_retry_base_ms << @intCast(attempt);
const delay_ms = indexRetryDelayMs(attempt);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry the calibration request during index builds

When the hosted lane is already returning 429, 5xx, or timeout responses as a build starts, the new retry loop is never reached because src/semantic_index.zig:714 still performs the required calibration through embedRemoteTexts directly. The entire index build consequently fails before its first chunk batch despite the advertised bounded backoff; route the calibration request through the same index-build retry path.

Useful? React with 👍 / 👎.

io.sleep(.fromMilliseconds(delay_ms), .awake) catch {};
continue;
};
}
unreachable;
}

pub fn indexRetryDelayMs(attempt: usize) u32 {
const shift: u5 = @intCast(@min(attempt, 16));
const scaled = @as(u64, index_embedding_retry_base_ms) << shift;
return @intCast(@min(scaled, index_embedding_retry_max_ms));
}

test "index retry backoff doubles, caps, and outlasts a one-minute quota window" {
const testing = std.testing;
try testing.expectEqual(@as(u32, 250), indexRetryDelayMs(0));
try testing.expectEqual(@as(u32, 500), indexRetryDelayMs(1));
try testing.expectEqual(index_embedding_retry_max_ms, indexRetryDelayMs(8));
try testing.expectEqual(index_embedding_retry_max_ms, indexRetryDelayMs(40));
var total: u64 = 0;
for (0..max_index_embedding_attempts - 1) |attempt| total += indexRetryDelayMs(attempt);
try testing.expect(total >= 60_000);
try testing.expect(total <= 120_000);
}

pub fn embedQueryRemote(
io: std.Io,
allocator: std.mem.Allocator,
Expand Down
26 changes: 11 additions & 15 deletions src/semantic_index.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ pub const slab_file_suffix = ".hmls";
pub const max_chunk_card_bytes: usize = 1024;
pub const chunk_source_bytes: usize = 832;
pub const max_chunks_per_file: usize = 256;
// Hosted-lane hill climbs: 256 chunks (2026-08-26) gave c1=12.4s, c2=9.4s,
// c4=6.1s, c8=16.7s. A 6,713-chunk React subset (2026-08-30) confirmed
// c1=180.8s, c2=98.9s, c4=68.8s, while c8 was aborted after 338s in retry/
// queue contention. Four remains the Pareto point; differently provisioned
// endpoints can still use the explicit override.
pub const default_parallel_batches: usize = 4;
// Hosted 100-item batches (2026-09-27, 1,570 chunks): c4=10.7s, c8=5.9s
// against 17.8s for the former 25-item c4 default. Repositories beyond the
// hosted per-minute quota are paced by the index retry backoff instead.
pub const default_parallel_batches: usize = 8;
pub const max_parallel_batches: usize = 8;
pub const default_search_results: usize = 24;
pub const max_records: usize = 250_000;
Expand Down Expand Up @@ -1657,15 +1655,13 @@ test "semantic ANN replacement removes only the previously referenced slab" {
try std.Io.Dir.cwd().access(io, new_path, .{});
}

test "semantic ANN wave uses the public 25-item batch boundary" {
// This is the hosted-lane Pareto point measured against the production
// TurboAPI/TEI queue. Raising the default to the supported maximum of 8
// regressed the 256-chunk end-to-end run from 6.1s to 16.7s. Keep the
// override for custom endpoints, but make an accidental default drift a
// release-gating test failure.
try std.testing.expectEqual(@as(usize, 4), default_parallel_batches);
try std.testing.expectEqual(@as(usize, 25), semantic.max_index_documents);
try std.testing.expectEqual(@as(usize, 100), default_parallel_batches * semantic.max_index_documents);
test "semantic ANN wave uses the hosted 100-item batch boundary" {
// Hosted batches carry 100 items with eight in flight (800-item waves).
// Keep the override for custom endpoints, but make an accidental default
// drift a release-gating test failure.
try std.testing.expectEqual(@as(usize, 8), default_parallel_batches);
try std.testing.expectEqual(@as(usize, 100), semantic.max_index_documents);
try std.testing.expectEqual(@as(usize, 800), default_parallel_batches * semantic.max_index_documents);
try std.testing.expectEqual(@as(usize, 1), embeddingBatchCount(semantic.max_index_documents));
try std.testing.expectEqual(@as(usize, 2), embeddingBatchCount(semantic.max_index_documents + 1));
try std.testing.expectEqual(
Expand Down
Loading