mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-13 08:15:14 +02:00
* docs: add npm managed python runtime design * build: add bundled python runtime wheel builder * build: make local embedding dependencies optional * build: bundle python runtime wheel in cli artifacts * build: track bundled python runtime release artifact * test: verify bundled python runtime wheel * docs: add plan for bundled python runtime wheel * test: cover managed python runtime lifecycle * feat: add managed python runtime installer * feat: add runtime command runner * feat: expose runtime management commands * test: verify managed python runtime commands * docs: add plan for managed python runtime installer * feat: add managed python command helper * feat: use managed runtime for sl query compute * feat: route sl query managed runtime policy * docs: add plan for managed runtime sl query integration * feat: add managed runtime daemon metadata * feat: manage python daemon lifecycle * feat: add runtime daemon start stop commands * fix: verify managed runtime daemon lifecycle * docs: add plan for managed runtime daemon lifecycle * feat: add managed local embeddings config marker * feat: add managed local embeddings daemon helper * feat: use managed runtime for local embedding setup * feat: pass managed runtime policy through setup * docs: add plan for managed local embeddings runtime * feat: read CLI package metadata dynamically * feat: assemble public kaelio ktx npm package * feat: release one public kaelio ktx npm artifact * test: cover public kaelio ktx package invocations * chore: verify public kaelio ktx package artifacts * docs: add plan for public kaelio ktx npm package * test: verify managed runtime in public package smoke * test: finalize managed runtime release smoke * docs: add plan for managed runtime release smoke * test: specify local embeddings release smoke * feat: add local embeddings runtime smoke * chore: register local embeddings smoke * fix: verify local embeddings smoke * fix: restore artifact smoke python env helper * docs: add plan for managed local embeddings release smoke * refactor: share managed runtime install policy parsing * feat: use managed runtime for agent semantic queries * feat: use managed runtime for MCP semantic compute * docs: add plan for managed agent and MCP semantic runtime * feat(cli): add managed daemon HTTP helpers * feat(cli): route local adapters through managed daemon * feat(cli): use managed daemon for ingest helpers * feat(cli): pass managed daemon options to scan * feat(context): pass MCP ingest pull config options * feat(cli): pass managed daemon options to serve ingest * test: verify managed local ingest daemon runtime * docs: add plan for managed local ingest daemon runtime * docs: align managed runtime examples * docs: add plan for managed runtime docs cleanup * test: cover published package runtime smoke commands * test: validate published package smoke outputs * docs: add plan for published package runtime smoke * build: stamp public npm package version * release: add npm public release policy * release: add guarded npm publish script * release: document public npm release handoff * docs: add plan for public npm release handoff * test: cover managed runtime prune in package smoke * docs: document managed runtime prune * docs: add plan for managed runtime prune smoke and docs * chore: encode uv runtime prerequisite policy * fix: clarify missing uv runtime error * docs: document uv runtime prerequisite * docs: add plan for uv runtime prerequisite contract * refactor: limit release artifacts to public package runtime * chore: align release policy with bundled runtime wheel * docs: describe single public runtime artifact surface * test: verify single public runtime artifact contract * docs: add plan for single public runtime artifact cleanup * fix: align local embeddings smoke with public version * docs: add plan for local embeddings smoke public version * release: soft-launch as @kaelio/ktx@0.1.0-rc.0 on next tag Publish target moves to the pre-release version 0.1.0-rc.0 under the next dist-tag so npm install @kaelio/ktx (which resolves to latest) does not pick up the soft-launch build. Users opt in via @kaelio/ktx@next. * Fix release script boundary checks * Remove PostHog from public package bundle
1122 lines
34 KiB
Markdown
1122 lines
34 KiB
Markdown
# Managed Local Embeddings Runtime Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use
|
|
> superpowers:subagent-driven-development (recommended) or
|
|
> superpowers:executing-plans to implement this plan task-by-task. Steps use
|
|
> checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Make local `sentence-transformers` embedding setup use the
|
|
KTX-managed Python runtime and daemon instead of requiring users to start a
|
|
manual `ktx-daemon` process.
|
|
|
|
**Architecture:** Add one managed local-embedding helper in the CLI that
|
|
prompts or fails according to the existing runtime install policy, starts the
|
|
managed daemon with the `local-embeddings` feature, and returns the daemon URL
|
|
for health checks. Store a stable managed-runtime marker in `ktx.yaml`, and
|
|
teach context embedding config resolution to turn that marker into a daemon URL
|
|
only when the CLI has provided one through the environment.
|
|
|
|
**Tech Stack:** TypeScript, Vitest, Commander, `@clack/prompts`, KTX managed
|
|
Python runtime commands, `@ktx/llm` embedding health checks.
|
|
|
|
---
|
|
|
|
## Existing status
|
|
|
|
This plan is based on
|
|
`docs/superpowers/specs/2026-05-11-npm-managed-python-runtime-design.md`.
|
|
|
|
Existing plans based on the spec:
|
|
|
|
- `docs/superpowers/plans/2026-05-11-bundled-python-runtime-wheel.md` is
|
|
implemented. The worktree contains the runtime wheel builder, runtime wheel
|
|
packaging, the `kaelio-ktx` Python artifact policy entry, and matching
|
|
artifact tests.
|
|
- `docs/superpowers/plans/2026-05-11-managed-python-runtime-installer.md` is
|
|
implemented. The worktree contains `managed-python-runtime.ts`, the runtime
|
|
command runner, `runtime install`, `status`, `doctor`, and `prune` command
|
|
registration, and matching CLI tests.
|
|
- `docs/superpowers/plans/2026-05-11-managed-python-runtime-command-integration.md`
|
|
is implemented. The worktree contains `managed-python-command.ts`, `ktx sl
|
|
query` runtime policy flags, schema validation, and matching `sl` tests.
|
|
- `docs/superpowers/plans/2026-05-11-managed-python-runtime-daemon-lifecycle.md`
|
|
is implemented. The worktree contains `managed-python-daemon.ts`, daemon
|
|
state paths in the runtime layout, `runtime start`, `runtime stop`, Python
|
|
`/health` version metadata, and matching TypeScript and Python tests.
|
|
|
|
Spec requirements still outside this plan:
|
|
|
|
- Public npm package surface rename from `@ktx/cli` to `@kaelio/ktx`.
|
|
- Managed runtime usage for non-embedding Python-backed command paths beyond
|
|
`ktx sl query`.
|
|
- Release smoke coverage for `npx @kaelio/ktx ...` invocation modes.
|
|
|
|
This plan implements the next local-embedding runtime slice:
|
|
|
|
- Selecting local embeddings installs only the `local-embeddings` runtime
|
|
feature.
|
|
- Local embedding setup starts or reuses the managed HTTP daemon.
|
|
- `--yes` installs and starts without prompting.
|
|
- `--no-input` fails with an exact preparation command when the managed local
|
|
embedding runtime is missing.
|
|
- Project config records a managed local embedding marker instead of a random
|
|
daemon port.
|
|
- Context embedding resolution only resolves the marker when the CLI provides
|
|
the active daemon URL.
|
|
|
|
## File structure
|
|
|
|
- Modify `packages/context/src/llm/local-config.ts`: define the managed local
|
|
embeddings marker and environment variable, and resolve that marker to a
|
|
runtime daemon URL.
|
|
- Modify `packages/context/src/llm/local-config.test.ts`: cover marker
|
|
resolution, missing daemon URL behavior, and provider construction.
|
|
- Modify `packages/context/src/llm/index.ts`: export the marker constants.
|
|
- Modify `packages/context/src/package-exports.test.ts`: assert root exports
|
|
expose the marker constants.
|
|
- Create `packages/cli/src/managed-local-embeddings.ts`: start or reuse the
|
|
managed daemon with `local-embeddings` and build health/project configs.
|
|
- Create `packages/cli/src/managed-local-embeddings.test.ts`: cover ready,
|
|
`--yes`, prompt, and `--no-input` behavior.
|
|
- Modify `packages/cli/src/setup-embeddings.ts`: use the managed helper for
|
|
local embeddings and persist the managed marker.
|
|
- Modify `packages/cli/src/setup-embeddings.test.ts`: update local embedding
|
|
setup expectations and no-input failure behavior.
|
|
- Modify `packages/cli/src/setup.ts`: pass CLI version and runtime install
|
|
policy into the embeddings step.
|
|
- Modify `packages/cli/src/commands/setup-commands.ts`: attach package version
|
|
to setup runs.
|
|
- Modify `packages/cli/src/cli-program.ts`: attach package version to the bare
|
|
interactive setup path.
|
|
- Modify `packages/cli/src/index.ts`: export the managed local embedding helper
|
|
for tests and programmatic use.
|
|
- Modify `packages/cli/src/index.test.ts` and `packages/cli/src/setup.test.ts`:
|
|
update setup argument expectations for `cliVersion`.
|
|
|
|
### Task 1: Add managed embedding marker resolution in context
|
|
|
|
**Files:**
|
|
|
|
- Modify: `packages/context/src/llm/local-config.test.ts`
|
|
- Modify: `packages/context/src/llm/local-config.ts`
|
|
- Modify: `packages/context/src/llm/index.ts`
|
|
- Modify: `packages/context/src/package-exports.test.ts`
|
|
|
|
- [ ] **Step 1: Write failing marker resolution tests**
|
|
|
|
In `packages/context/src/llm/local-config.test.ts`, extend the import from
|
|
`./local-config.js` so it includes the new constants:
|
|
|
|
```typescript
|
|
import {
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
|
createLocalKtxEmbeddingProviderFromConfig,
|
|
createLocalKtxLlmProviderFromConfig,
|
|
resolveLocalKtxEmbeddingConfig,
|
|
resolveLocalKtxLlmConfig,
|
|
} from './local-config.js';
|
|
```
|
|
|
|
Add these tests inside `describe('local KTX embedding config', () => { ... })`
|
|
after the existing `resolves sentence-transformers config` test:
|
|
|
|
```typescript
|
|
it('resolves managed sentence-transformers config from the CLI-provided daemon URL', () => {
|
|
const config: KtxProjectEmbeddingConfig = {
|
|
backend: 'sentence-transformers',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
sentenceTransformers: {
|
|
base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
pathPrefix: '',
|
|
},
|
|
batchSize: 32,
|
|
};
|
|
|
|
expect(
|
|
resolveLocalKtxEmbeddingConfig(config, {
|
|
[MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: 'http://127.0.0.1:61234',
|
|
}),
|
|
).toEqual({
|
|
backend: 'sentence-transformers',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
sentenceTransformers: { baseURL: 'http://127.0.0.1:61234', pathPrefix: '' },
|
|
batchSize: 32,
|
|
});
|
|
});
|
|
|
|
it('returns null for managed sentence-transformers when no daemon URL is available', () => {
|
|
const config: KtxProjectEmbeddingConfig = {
|
|
backend: 'sentence-transformers',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
sentenceTransformers: {
|
|
base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
pathPrefix: '',
|
|
},
|
|
};
|
|
|
|
expect(resolveLocalKtxEmbeddingConfig(config, {})).toBeNull();
|
|
});
|
|
```
|
|
|
|
In `packages/context/src/package-exports.test.ts`, add these assertions after
|
|
the existing `root.createLocalKtxEmbeddingProviderFromConfig` assertion:
|
|
|
|
```typescript
|
|
expect(root.MANAGED_SENTENCE_TRANSFORMERS_BASE_URL).toBe('managed:local-embeddings');
|
|
expect(root.MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV).toBe(
|
|
'KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL',
|
|
);
|
|
```
|
|
|
|
- [ ] **Step 2: Run the failing context tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/context run test -- src/llm/local-config.test.ts src/package-exports.test.ts
|
|
```
|
|
|
|
Expected: FAIL with missing exports for
|
|
`MANAGED_SENTENCE_TRANSFORMERS_BASE_URL` and
|
|
`MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV`.
|
|
|
|
- [ ] **Step 3: Implement marker resolution**
|
|
|
|
In `packages/context/src/llm/local-config.ts`, add these exports after the
|
|
`LocalConfigDeps` interface:
|
|
|
|
```typescript
|
|
export const MANAGED_SENTENCE_TRANSFORMERS_BASE_URL = 'managed:local-embeddings';
|
|
export const MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV = 'KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL';
|
|
```
|
|
|
|
Add this helper before `resolveLocalKtxEmbeddingConfig`:
|
|
|
|
```typescript
|
|
function resolveSentenceTransformersBaseUrl(value: string | undefined, env: NodeJS.ProcessEnv): string | undefined {
|
|
if (!value) {
|
|
return undefined;
|
|
}
|
|
if (value === MANAGED_SENTENCE_TRANSFORMERS_BASE_URL) {
|
|
return resolveOptional(`env:${MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV}`, env);
|
|
}
|
|
return value;
|
|
}
|
|
```
|
|
|
|
Replace `resolveLocalKtxEmbeddingConfig` with this implementation:
|
|
|
|
```typescript
|
|
export function resolveLocalKtxEmbeddingConfig(
|
|
config: KtxProjectEmbeddingConfig,
|
|
env: NodeJS.ProcessEnv,
|
|
): KtxEmbeddingConfig | null {
|
|
if (config.backend === 'none') {
|
|
return null;
|
|
}
|
|
if (config.backend === 'sentence-transformers') {
|
|
const baseURL = resolveSentenceTransformersBaseUrl(config.sentenceTransformers?.base_url, env);
|
|
if (!baseURL) {
|
|
return null;
|
|
}
|
|
return {
|
|
backend: config.backend,
|
|
model: config.model ?? 'all-MiniLM-L6-v2',
|
|
dimensions: config.dimensions,
|
|
sentenceTransformers: {
|
|
baseURL,
|
|
pathPrefix: config.sentenceTransformers?.pathPrefix,
|
|
},
|
|
batchSize: config.batchSize,
|
|
};
|
|
}
|
|
return {
|
|
backend: config.backend,
|
|
model: config.model ?? 'deterministic',
|
|
dimensions: config.dimensions,
|
|
...(resolvedProviderConfig(config.openai, env) ? { openai: resolvedProviderConfig(config.openai, env) } : {}),
|
|
batchSize: config.batchSize,
|
|
};
|
|
}
|
|
```
|
|
|
|
In `packages/context/src/llm/index.ts`, add the new constants to the existing
|
|
export from `./local-config.js`:
|
|
|
|
```typescript
|
|
export {
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
|
createLocalKtxEmbeddingProviderFromConfig,
|
|
createLocalKtxLlmProviderFromConfig,
|
|
resolveLocalKtxEmbeddingConfig,
|
|
resolveLocalKtxLlmConfig,
|
|
} from './local-config.js';
|
|
```
|
|
|
|
- [ ] **Step 4: Verify the context marker tests pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/context run test -- src/llm/local-config.test.ts src/package-exports.test.ts
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add packages/context/src/llm/local-config.ts packages/context/src/llm/local-config.test.ts packages/context/src/llm/index.ts packages/context/src/package-exports.test.ts
|
|
git commit -m "feat: add managed local embeddings config marker"
|
|
```
|
|
|
|
### Task 2: Add the managed local embeddings CLI helper
|
|
|
|
**Files:**
|
|
|
|
- Create: `packages/cli/src/managed-local-embeddings.test.ts`
|
|
- Create: `packages/cli/src/managed-local-embeddings.ts`
|
|
- Modify: `packages/cli/src/index.ts`
|
|
|
|
- [ ] **Step 1: Write the failing helper tests**
|
|
|
|
Create `packages/cli/src/managed-local-embeddings.test.ts` with this content:
|
|
|
|
```typescript
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import {
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
|
} from '@ktx/context';
|
|
import {
|
|
ensureManagedLocalEmbeddingsDaemon,
|
|
managedLocalEmbeddingHealthConfig,
|
|
managedLocalEmbeddingProjectConfig,
|
|
} from './managed-local-embeddings.js';
|
|
import type { ManagedPythonCommandRuntime } from './managed-python-command.js';
|
|
import type { ManagedPythonDaemonStartResult } from './managed-python-daemon.js';
|
|
|
|
function makeIo() {
|
|
let stdout = '';
|
|
let stderr = '';
|
|
return {
|
|
io: {
|
|
stdout: {
|
|
write: (chunk: string) => {
|
|
stdout += chunk;
|
|
},
|
|
},
|
|
stderr: {
|
|
write: (chunk: string) => {
|
|
stderr += chunk;
|
|
},
|
|
},
|
|
},
|
|
stdout: () => stdout,
|
|
stderr: () => stderr,
|
|
};
|
|
}
|
|
|
|
function runtime(): ManagedPythonCommandRuntime {
|
|
return {
|
|
layout: {
|
|
cliVersion: '0.2.0',
|
|
runtimeRoot: '/runtime',
|
|
versionDir: '/runtime/0.2.0',
|
|
venvDir: '/runtime/0.2.0/.venv',
|
|
manifestPath: '/runtime/0.2.0/manifest.json',
|
|
installLogPath: '/runtime/0.2.0/install.log',
|
|
assetDir: '/assets/python',
|
|
assetManifestPath: '/assets/python/manifest.json',
|
|
pythonPath: '/runtime/0.2.0/.venv/bin/python',
|
|
daemonPath: '/runtime/0.2.0/.venv/bin/ktx-daemon',
|
|
daemonStatePath: '/runtime/0.2.0/daemon.json',
|
|
daemonStdoutPath: '/runtime/0.2.0/daemon.stdout.log',
|
|
daemonStderrPath: '/runtime/0.2.0/daemon.stderr.log',
|
|
},
|
|
manifest: {
|
|
schemaVersion: 1,
|
|
cliVersion: '0.2.0',
|
|
installedAt: '2026-05-11T00:00:00.000Z',
|
|
asset: {
|
|
schemaVersion: 1,
|
|
distributionName: 'kaelio-ktx',
|
|
normalizedName: 'kaelio_ktx',
|
|
version: '0.2.0',
|
|
wheel: {
|
|
file: 'kaelio_ktx-0.2.0-py3-none-any.whl',
|
|
sha256: 'a'.repeat(64),
|
|
bytes: 123,
|
|
},
|
|
},
|
|
features: ['core', 'local-embeddings'],
|
|
python: {
|
|
executable: '/runtime/0.2.0/.venv/bin/python',
|
|
daemonExecutable: '/runtime/0.2.0/.venv/bin/ktx-daemon',
|
|
},
|
|
installLog: '/runtime/0.2.0/install.log',
|
|
},
|
|
};
|
|
}
|
|
|
|
function daemonResult(status: 'started' | 'reused' = 'reused'): ManagedPythonDaemonStartResult {
|
|
return {
|
|
status,
|
|
layout: runtime().layout,
|
|
baseUrl: 'http://127.0.0.1:61234',
|
|
state: {
|
|
schemaVersion: 1,
|
|
pid: 12345,
|
|
host: '127.0.0.1',
|
|
port: 61234,
|
|
version: '0.2.0',
|
|
features: ['core', 'local-embeddings'],
|
|
startedAt: '2026-05-11T00:00:00.000Z',
|
|
stdoutLog: '/runtime/0.2.0/daemon.stdout.log',
|
|
stderrLog: '/runtime/0.2.0/daemon.stderr.log',
|
|
},
|
|
};
|
|
}
|
|
|
|
describe('managedLocalEmbeddingProjectConfig', () => {
|
|
it('uses a stable managed runtime marker instead of a random daemon port', () => {
|
|
expect(
|
|
managedLocalEmbeddingProjectConfig({
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
}),
|
|
).toEqual({
|
|
backend: 'sentence-transformers',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
sentenceTransformers: {
|
|
base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
pathPrefix: '',
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('managedLocalEmbeddingHealthConfig', () => {
|
|
it('uses the active managed daemon URL for the immediate health check', () => {
|
|
expect(
|
|
managedLocalEmbeddingHealthConfig({
|
|
baseUrl: 'http://127.0.0.1:61234',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
}),
|
|
).toEqual({
|
|
backend: 'sentence-transformers',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
sentenceTransformers: { baseURL: 'http://127.0.0.1:61234', pathPrefix: '' },
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('ensureManagedLocalEmbeddingsDaemon', () => {
|
|
it('ensures the local-embeddings feature and starts the managed daemon', async () => {
|
|
const io = makeIo();
|
|
const ensureRuntime = vi.fn(async () => runtime());
|
|
const startDaemon = vi.fn(async () => daemonResult('started'));
|
|
|
|
await expect(
|
|
ensureManagedLocalEmbeddingsDaemon({
|
|
cliVersion: '0.2.0',
|
|
installPolicy: 'auto',
|
|
io: io.io,
|
|
ensureRuntime,
|
|
startDaemon,
|
|
}),
|
|
).resolves.toEqual({
|
|
baseUrl: 'http://127.0.0.1:61234',
|
|
env: {
|
|
[MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: 'http://127.0.0.1:61234',
|
|
},
|
|
});
|
|
|
|
expect(ensureRuntime).toHaveBeenCalledWith({
|
|
cliVersion: '0.2.0',
|
|
installPolicy: 'auto',
|
|
io: io.io,
|
|
feature: 'local-embeddings',
|
|
});
|
|
expect(startDaemon).toHaveBeenCalledWith({
|
|
cliVersion: '0.2.0',
|
|
features: ['local-embeddings'],
|
|
force: false,
|
|
});
|
|
expect(io.stderr()).toContain('Started KTX local embeddings daemon: http://127.0.0.1:61234');
|
|
});
|
|
|
|
it('reuses an already running daemon without reporting a new start', async () => {
|
|
const io = makeIo();
|
|
|
|
await ensureManagedLocalEmbeddingsDaemon({
|
|
cliVersion: '0.2.0',
|
|
installPolicy: 'prompt',
|
|
io: io.io,
|
|
ensureRuntime: vi.fn(async () => runtime()),
|
|
startDaemon: vi.fn(async () => daemonResult('reused')),
|
|
});
|
|
|
|
expect(io.stderr()).toContain('Using KTX local embeddings daemon: http://127.0.0.1:61234');
|
|
});
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the failing helper tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/cli run test -- src/managed-local-embeddings.test.ts
|
|
```
|
|
|
|
Expected: FAIL with an import error for
|
|
`./managed-local-embeddings.js`.
|
|
|
|
- [ ] **Step 3: Implement the helper**
|
|
|
|
Create `packages/cli/src/managed-local-embeddings.ts` with this content:
|
|
|
|
```typescript
|
|
import {
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV,
|
|
} from '@ktx/context';
|
|
import type { KtxProjectEmbeddingConfig } from '@ktx/context/project';
|
|
import type { KtxEmbeddingConfig } from '@ktx/llm';
|
|
import type { KtxCliIo } from './cli-runtime.js';
|
|
import {
|
|
ensureManagedPythonCommandRuntime,
|
|
type KtxManagedPythonInstallPolicy,
|
|
type ManagedPythonCommandRuntime,
|
|
} from './managed-python-command.js';
|
|
import { startManagedPythonDaemon, type ManagedPythonDaemonStartResult } from './managed-python-daemon.js';
|
|
|
|
export interface ManagedLocalEmbeddingsDaemon {
|
|
baseUrl: string;
|
|
env: Record<typeof MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV, string>;
|
|
}
|
|
|
|
export interface ManagedLocalEmbeddingsOptions {
|
|
cliVersion: string;
|
|
installPolicy: KtxManagedPythonInstallPolicy;
|
|
io: KtxCliIo;
|
|
ensureRuntime?: (options: {
|
|
cliVersion: string;
|
|
installPolicy: KtxManagedPythonInstallPolicy;
|
|
io: KtxCliIo;
|
|
feature: 'local-embeddings';
|
|
}) => Promise<ManagedPythonCommandRuntime>;
|
|
startDaemon?: (options: {
|
|
cliVersion: string;
|
|
features: ['local-embeddings'];
|
|
force: boolean;
|
|
}) => Promise<ManagedPythonDaemonStartResult>;
|
|
}
|
|
|
|
export function managedLocalEmbeddingProjectConfig(input: {
|
|
model: string;
|
|
dimensions: number;
|
|
}): KtxProjectEmbeddingConfig {
|
|
return {
|
|
backend: 'sentence-transformers',
|
|
model: input.model,
|
|
dimensions: input.dimensions,
|
|
sentenceTransformers: {
|
|
base_url: MANAGED_SENTENCE_TRANSFORMERS_BASE_URL,
|
|
pathPrefix: '',
|
|
},
|
|
};
|
|
}
|
|
|
|
export function managedLocalEmbeddingHealthConfig(input: {
|
|
baseUrl: string;
|
|
model: string;
|
|
dimensions: number;
|
|
}): KtxEmbeddingConfig {
|
|
return {
|
|
backend: 'sentence-transformers',
|
|
model: input.model,
|
|
dimensions: input.dimensions,
|
|
sentenceTransformers: {
|
|
baseURL: input.baseUrl,
|
|
pathPrefix: '',
|
|
},
|
|
};
|
|
}
|
|
|
|
export async function ensureManagedLocalEmbeddingsDaemon(
|
|
options: ManagedLocalEmbeddingsOptions,
|
|
): Promise<ManagedLocalEmbeddingsDaemon> {
|
|
const ensureRuntime = options.ensureRuntime ?? ensureManagedPythonCommandRuntime;
|
|
const startDaemon = options.startDaemon ?? startManagedPythonDaemon;
|
|
|
|
await ensureRuntime({
|
|
cliVersion: options.cliVersion,
|
|
installPolicy: options.installPolicy,
|
|
io: options.io,
|
|
feature: 'local-embeddings',
|
|
});
|
|
const daemon = await startDaemon({
|
|
cliVersion: options.cliVersion,
|
|
features: ['local-embeddings'],
|
|
force: false,
|
|
});
|
|
|
|
const verb = daemon.status === 'started' ? 'Started' : 'Using';
|
|
options.io.stderr.write(`${verb} KTX local embeddings daemon: ${daemon.baseUrl}\n`);
|
|
|
|
return {
|
|
baseUrl: daemon.baseUrl,
|
|
env: {
|
|
[MANAGED_SENTENCE_TRANSFORMERS_BASE_URL_ENV]: daemon.baseUrl,
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
In `packages/cli/src/index.ts`, add this export after the existing
|
|
`managed-python-daemon.js` exports:
|
|
|
|
```typescript
|
|
export {
|
|
ensureManagedLocalEmbeddingsDaemon,
|
|
managedLocalEmbeddingHealthConfig,
|
|
managedLocalEmbeddingProjectConfig,
|
|
type ManagedLocalEmbeddingsDaemon,
|
|
type ManagedLocalEmbeddingsOptions,
|
|
} from './managed-local-embeddings.js';
|
|
```
|
|
|
|
- [ ] **Step 4: Verify helper tests pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/cli run test -- src/managed-local-embeddings.test.ts
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 5: Commit**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add packages/cli/src/managed-local-embeddings.ts packages/cli/src/managed-local-embeddings.test.ts packages/cli/src/index.ts
|
|
git commit -m "feat: add managed local embeddings daemon helper"
|
|
```
|
|
|
|
### Task 3: Wire setup embeddings to the managed runtime
|
|
|
|
**Files:**
|
|
|
|
- Modify: `packages/cli/src/setup-embeddings.ts`
|
|
- Modify: `packages/cli/src/setup-embeddings.test.ts`
|
|
|
|
- [ ] **Step 1: Write failing setup tests for managed local embeddings**
|
|
|
|
In `packages/cli/src/setup-embeddings.test.ts`, update the import from
|
|
`./setup-embeddings.js` so it also imports the managed install policy type:
|
|
|
|
```typescript
|
|
import {
|
|
type KtxSetupEmbeddingsPromptAdapter,
|
|
runKtxSetupEmbeddingsStep,
|
|
} from './setup-embeddings.js';
|
|
```
|
|
|
|
Add this helper near `makePromptAdapter`:
|
|
|
|
```typescript
|
|
function managedDaemon(baseUrl = 'http://127.0.0.1:61234') {
|
|
return {
|
|
baseUrl,
|
|
env: {
|
|
KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL: baseUrl,
|
|
},
|
|
};
|
|
}
|
|
```
|
|
|
|
In every `runKtxSetupEmbeddingsStep` call that does not inject an `embeddingBackend:
|
|
'openai'`, add these arguments:
|
|
|
|
```typescript
|
|
cliVersion: '0.2.0',
|
|
runtimeInstallPolicy: 'auto',
|
|
```
|
|
|
|
In the test named `configures local sentence-transformers embeddings after
|
|
interactive selection`, add this dependency:
|
|
|
|
```typescript
|
|
const ensureLocalEmbeddings = vi.fn(async () => managedDaemon());
|
|
```
|
|
|
|
Pass it in the deps object:
|
|
|
|
```typescript
|
|
{ prompts, env: {}, healthCheck, ensureLocalEmbeddings },
|
|
```
|
|
|
|
Replace the expected health check config in that test with:
|
|
|
|
```typescript
|
|
expect(ensureLocalEmbeddings).toHaveBeenCalledWith({
|
|
cliVersion: '0.2.0',
|
|
installPolicy: 'auto',
|
|
io: io.io,
|
|
});
|
|
expect(healthCheck).toHaveBeenCalledWith({
|
|
backend: 'sentence-transformers',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
sentenceTransformers: { baseURL: 'http://127.0.0.1:61234', pathPrefix: '' },
|
|
});
|
|
```
|
|
|
|
Replace the persisted local embedding expectation in that test with:
|
|
|
|
```typescript
|
|
expect(config.ingest.embeddings).toMatchObject({
|
|
backend: 'sentence-transformers',
|
|
model: 'all-MiniLM-L6-v2',
|
|
dimensions: 384,
|
|
sentenceTransformers: { base_url: 'managed:local-embeddings', pathPrefix: '' },
|
|
});
|
|
```
|
|
|
|
Add this new test after the existing non-interactive local embeddings test:
|
|
|
|
```typescript
|
|
it('fails non-interactive local setup when the managed local embeddings runtime is missing', async () => {
|
|
const io = makeIo();
|
|
const ensureLocalEmbeddings = vi.fn(async () => {
|
|
throw new Error(
|
|
'KTX Python runtime is required for this command. Run: ktx runtime install --feature local-embeddings --yes',
|
|
);
|
|
});
|
|
|
|
const result = await runKtxSetupEmbeddingsStep(
|
|
{
|
|
projectDir: tempDir,
|
|
inputMode: 'disabled',
|
|
cliVersion: '0.2.0',
|
|
runtimeInstallPolicy: 'never',
|
|
skipEmbeddings: false,
|
|
},
|
|
io.io,
|
|
{ env: {}, ensureLocalEmbeddings },
|
|
);
|
|
|
|
expect(result.status).toBe('failed');
|
|
expect(io.stderr()).toContain(
|
|
'KTX Python runtime is required for this command. Run: ktx runtime install --feature local-embeddings --yes',
|
|
);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the failing setup tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/cli run test -- src/setup-embeddings.test.ts
|
|
```
|
|
|
|
Expected: FAIL because `KtxSetupEmbeddingsArgs` has no `cliVersion` or
|
|
`runtimeInstallPolicy`, and `KtxSetupEmbeddingsDeps` has no
|
|
`ensureLocalEmbeddings`.
|
|
|
|
- [ ] **Step 3: Update setup embeddings types and imports**
|
|
|
|
In `packages/cli/src/setup-embeddings.ts`, add these imports:
|
|
|
|
```typescript
|
|
import type { KtxManagedPythonInstallPolicy } from './managed-python-command.js';
|
|
import {
|
|
ensureManagedLocalEmbeddingsDaemon,
|
|
managedLocalEmbeddingHealthConfig,
|
|
managedLocalEmbeddingProjectConfig,
|
|
type ManagedLocalEmbeddingsDaemon,
|
|
} from './managed-local-embeddings.js';
|
|
```
|
|
|
|
Add these fields to `KtxSetupEmbeddingsArgs` after `inputMode`:
|
|
|
|
```typescript
|
|
cliVersion: string;
|
|
runtimeInstallPolicy: KtxManagedPythonInstallPolicy;
|
|
```
|
|
|
|
Add this dependency to `KtxSetupEmbeddingsDeps`:
|
|
|
|
```typescript
|
|
ensureLocalEmbeddings?: (options: {
|
|
cliVersion: string;
|
|
installPolicy: KtxManagedPythonInstallPolicy;
|
|
io: KtxCliIo;
|
|
}) => Promise<ManagedLocalEmbeddingsDaemon>;
|
|
```
|
|
|
|
- [ ] **Step 4: Replace manual local daemon messaging and config**
|
|
|
|
In `packages/cli/src/setup-embeddings.ts`, remove these constants:
|
|
|
|
```typescript
|
|
const LOCAL_EMBEDDING_DAEMON_COMMAND = 'ktx-daemon serve-http --host 127.0.0.1 --port 8765';
|
|
const LOCAL_EMBEDDING_DAEMON_DEV_COMMAND =
|
|
'cd ktx && source .venv/bin/activate && uv run ktx-daemon serve-http --host 127.0.0.1 --port 8765';
|
|
```
|
|
|
|
Replace `localEmbeddingSetupMessage` with:
|
|
|
|
```typescript
|
|
function localEmbeddingSetupMessage(message: string): string {
|
|
return [
|
|
`Local embedding health check failed: ${message}`,
|
|
'Local embeddings use the KTX-managed Python runtime.',
|
|
'Prepare the runtime with: ktx runtime start --feature local-embeddings',
|
|
'Use --yes with setup to install and start the runtime without prompting.',
|
|
'The first run may download Python packages and the all-MiniLM-L6-v2 model.',
|
|
].join('\n');
|
|
}
|
|
```
|
|
|
|
Inside `runKtxSetupEmbeddingsStep`, before building `healthConfig`, add this
|
|
block after the OpenAI credential block:
|
|
|
|
```typescript
|
|
let managedLocalEmbeddings: ManagedLocalEmbeddingsDaemon | undefined;
|
|
if (selectedBackend === LOCAL_EMBEDDING_BACKEND) {
|
|
const ensureLocalEmbeddings = deps.ensureLocalEmbeddings ?? ensureManagedLocalEmbeddingsDaemon;
|
|
try {
|
|
managedLocalEmbeddings = await ensureLocalEmbeddings({
|
|
cliVersion: args.cliVersion,
|
|
installPolicy: args.runtimeInstallPolicy,
|
|
io,
|
|
});
|
|
} catch (error) {
|
|
io.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
return { status: 'failed', projectDir: args.projectDir };
|
|
}
|
|
}
|
|
```
|
|
|
|
Replace the `healthConfig` assignment with:
|
|
|
|
```typescript
|
|
const healthConfig =
|
|
selectedBackend === LOCAL_EMBEDDING_BACKEND && managedLocalEmbeddings
|
|
? managedLocalEmbeddingHealthConfig({
|
|
baseUrl: managedLocalEmbeddings.baseUrl,
|
|
model,
|
|
dimensions,
|
|
})
|
|
: buildHealthConfig({
|
|
backend: selectedBackend,
|
|
model,
|
|
dimensions,
|
|
credentialValue,
|
|
});
|
|
```
|
|
|
|
Replace the successful local persistence call inside `if (health.ok) { ... }`
|
|
with:
|
|
|
|
```typescript
|
|
await persistEmbeddingConfig(
|
|
args.projectDir,
|
|
selectedBackend === LOCAL_EMBEDDING_BACKEND
|
|
? managedLocalEmbeddingProjectConfig({ model, dimensions })
|
|
: buildProjectEmbeddingConfig({
|
|
backend: selectedBackend,
|
|
model,
|
|
dimensions,
|
|
credentialRef,
|
|
}),
|
|
);
|
|
```
|
|
|
|
- [ ] **Step 5: Verify setup embeddings tests pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/cli run test -- src/setup-embeddings.test.ts src/managed-local-embeddings.test.ts
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add packages/cli/src/setup-embeddings.ts packages/cli/src/setup-embeddings.test.ts
|
|
git commit -m "feat: use managed runtime for local embedding setup"
|
|
```
|
|
|
|
### Task 4: Pass runtime policy and CLI version through setup commands
|
|
|
|
**Files:**
|
|
|
|
- Modify: `packages/cli/src/setup.ts`
|
|
- Modify: `packages/cli/src/commands/setup-commands.ts`
|
|
- Modify: `packages/cli/src/cli-program.ts`
|
|
- Modify: `packages/cli/src/setup.test.ts`
|
|
- Modify: `packages/cli/src/index.test.ts`
|
|
|
|
- [ ] **Step 1: Write failing setup argument expectations**
|
|
|
|
In `packages/cli/src/index.test.ts`, find the test that routes the main setup
|
|
command and add `cliVersion: '0.0.0-private'` to the expected setup run
|
|
argument object.
|
|
|
|
Add this assertion to the same test when `--yes` is present:
|
|
|
|
```typescript
|
|
yes: true,
|
|
cliVersion: '0.0.0-private',
|
|
```
|
|
|
|
In `packages/cli/src/setup.test.ts`, find the setup test that asserts the
|
|
embeddings runner arguments. Add these expected fields to the embeddings step
|
|
argument object:
|
|
|
|
```typescript
|
|
cliVersion: '0.2.0',
|
|
runtimeInstallPolicy: 'auto',
|
|
```
|
|
|
|
Add one focused unit test near the other setup flow tests:
|
|
|
|
```typescript
|
|
it('passes no-input runtime policy to the embeddings step', async () => {
|
|
const io = makeIo();
|
|
const embeddings = vi.fn(async () => ({ status: 'failed' as const, projectDir: tempDir }));
|
|
|
|
await expect(
|
|
runKtxSetup(
|
|
{
|
|
command: 'run',
|
|
projectDir: tempDir,
|
|
mode: 'existing',
|
|
agents: false,
|
|
agentScope: 'project',
|
|
agentInstallMode: 'cli',
|
|
skipAgents: true,
|
|
inputMode: 'disabled',
|
|
yes: false,
|
|
cliVersion: '0.2.0',
|
|
skipLlm: true,
|
|
skipEmbeddings: false,
|
|
databaseSchemas: [],
|
|
skipDatabases: true,
|
|
skipSources: true,
|
|
},
|
|
io.io,
|
|
{
|
|
project: {
|
|
run: vi.fn(async () => ({ status: 'ready' as const, projectDir: tempDir })),
|
|
},
|
|
embeddings,
|
|
},
|
|
),
|
|
).resolves.toBe(1);
|
|
|
|
expect(embeddings).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
cliVersion: '0.2.0',
|
|
runtimeInstallPolicy: 'never',
|
|
}),
|
|
io.io,
|
|
);
|
|
});
|
|
```
|
|
|
|
- [ ] **Step 2: Run the failing setup routing tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/cli run test -- src/setup.test.ts src/index.test.ts
|
|
```
|
|
|
|
Expected: FAIL because setup args do not carry `cliVersion` yet and embeddings
|
|
args do not derive `runtimeInstallPolicy`.
|
|
|
|
- [ ] **Step 3: Add `cliVersion` to setup run args**
|
|
|
|
In `packages/cli/src/setup.ts`, add this field to the run variant of
|
|
`KtxSetupArgs` immediately after `yes`:
|
|
|
|
```typescript
|
|
cliVersion: string;
|
|
```
|
|
|
|
Add this helper near the other setup helpers:
|
|
|
|
```typescript
|
|
function setupRuntimeInstallPolicy(args: Extract<KtxSetupArgs, { command: 'run' }>): 'prompt' | 'auto' | 'never' {
|
|
if (args.yes) {
|
|
return 'auto';
|
|
}
|
|
return args.inputMode === 'disabled' ? 'never' : 'prompt';
|
|
}
|
|
```
|
|
|
|
In the embeddings step call inside `runKtxSetupInner`, add:
|
|
|
|
```typescript
|
|
cliVersion: args.cliVersion,
|
|
runtimeInstallPolicy: setupRuntimeInstallPolicy(args),
|
|
```
|
|
|
|
- [ ] **Step 4: Pass package version from Commander and bare setup**
|
|
|
|
In `packages/cli/src/commands/setup-commands.ts`, add this field to the setup
|
|
run argument object:
|
|
|
|
```typescript
|
|
cliVersion: context.packageInfo.version,
|
|
```
|
|
|
|
Place it immediately after `yes: options.yes === true`.
|
|
|
|
In `packages/cli/src/cli-program.ts`, add this field to the bare interactive
|
|
setup argument object inside `runBareInteractiveCommand`:
|
|
|
|
```typescript
|
|
cliVersion: context.packageInfo.version,
|
|
```
|
|
|
|
Place it immediately after `yes: false`.
|
|
|
|
- [ ] **Step 5: Verify setup routing tests pass**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/cli run test -- src/setup.test.ts src/index.test.ts src/setup-embeddings.test.ts
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git add packages/cli/src/setup.ts packages/cli/src/commands/setup-commands.ts packages/cli/src/cli-program.ts packages/cli/src/setup.test.ts packages/cli/src/index.test.ts
|
|
git commit -m "feat: pass managed runtime policy through setup"
|
|
```
|
|
|
|
### Task 5: Final verification
|
|
|
|
**Files:**
|
|
|
|
- Verify: `packages/context/src/llm/local-config.ts`
|
|
- Verify: `packages/cli/src/managed-local-embeddings.ts`
|
|
- Verify: `packages/cli/src/setup-embeddings.ts`
|
|
- Verify: `packages/cli/src/setup.ts`
|
|
|
|
- [ ] **Step 1: Run focused context tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/context run test -- src/llm/local-config.test.ts src/package-exports.test.ts
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 2: Run focused CLI tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/cli run test -- src/managed-local-embeddings.test.ts src/setup-embeddings.test.ts src/setup.test.ts src/index.test.ts
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 3: Run TypeScript checks for changed packages**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/context run type-check
|
|
pnpm --filter @ktx/cli run type-check
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 4: Run package-level tests if type-check changed public exports**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
pnpm --filter @ktx/context run test
|
|
pnpm --filter @ktx/cli run test
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
- [ ] **Step 5: Run pre-commit for changed files**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
uv run pre-commit run --files packages/context/src/llm/local-config.ts packages/context/src/llm/local-config.test.ts packages/context/src/llm/index.ts packages/context/src/package-exports.test.ts packages/cli/src/managed-local-embeddings.ts packages/cli/src/managed-local-embeddings.test.ts packages/cli/src/setup-embeddings.ts packages/cli/src/setup-embeddings.test.ts packages/cli/src/setup.ts packages/cli/src/commands/setup-commands.ts packages/cli/src/cli-program.ts packages/cli/src/setup.test.ts packages/cli/src/index.test.ts packages/cli/src/index.ts
|
|
```
|
|
|
|
Expected: PASS. If pre-commit is unavailable because local hook versions are
|
|
missing, run the focused tests and type-check commands from steps 1 through 3
|
|
and record the pre-commit error.
|
|
|
|
- [ ] **Step 6: Commit final verification adjustments**
|
|
|
|
Run this only if final verification required small fixes:
|
|
|
|
```bash
|
|
git add packages/context/src/llm/local-config.ts packages/context/src/llm/local-config.test.ts packages/context/src/llm/index.ts packages/context/src/package-exports.test.ts packages/cli/src/managed-local-embeddings.ts packages/cli/src/managed-local-embeddings.test.ts packages/cli/src/setup-embeddings.ts packages/cli/src/setup-embeddings.test.ts packages/cli/src/setup.ts packages/cli/src/commands/setup-commands.ts packages/cli/src/cli-program.ts packages/cli/src/setup.test.ts packages/cli/src/index.test.ts packages/cli/src/index.ts
|
|
git commit -m "test: verify managed local embeddings runtime setup"
|
|
```
|
|
|
|
## Acceptance criteria
|
|
|
|
- `ktx setup --embedding-backend sentence-transformers --yes` installs the
|
|
`local-embeddings` runtime feature when needed, starts or reuses the managed
|
|
daemon, probes the active daemon URL, and writes `managed:local-embeddings`
|
|
to `ktx.yaml`.
|
|
- `ktx setup --embedding-backend sentence-transformers --no-input` fails with
|
|
the exact runtime preparation command when the runtime is missing.
|
|
- Existing OpenAI embedding setup behavior is unchanged.
|
|
- The project config no longer stores the daemon's random port.
|
|
- `resolveLocalKtxEmbeddingConfig` returns a usable `KtxEmbeddingConfig` for
|
|
managed local embeddings only when
|
|
`KTX_MANAGED_SENTENCE_TRANSFORMERS_BASE_URL` is present.
|
|
- Focused CLI and context tests pass.
|
|
|
|
## Self-review
|
|
|
|
- Spec coverage: This plan covers lazy `local-embeddings` installation after
|
|
local embeddings are selected, separate prompt/no-input behavior, and managed
|
|
daemon reuse for local embedding setup health checks.
|
|
- Placeholder scan: This plan contains concrete file paths, code snippets,
|
|
commands, expected outcomes, and commit commands.
|
|
- Type consistency: The new `ManagedLocalEmbeddingsDaemon` type, managed marker
|
|
constants, setup argument fields, and helper function names are used
|
|
consistently across tasks.
|