From 3de32c43a1aba95e113ee565b59ac638faeecd34 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Fri, 15 May 2026 13:04:16 +0200 Subject: [PATCH] feat: wire claude-code agent runner backend --- .../docs/concepts/the-context-layer.mdx | 11 ++++ .../docs/getting-started/quickstart.mdx | 24 ++++++- packages/cli/package.json | 1 + packages/cli/src/commands/setup-commands.ts | 12 +++- packages/cli/src/context-build-view.test.ts | 1 + packages/cli/src/index.test.ts | 35 ++++++++++ packages/cli/src/ingest.test-utils.ts | 11 ++-- packages/cli/src/setup-context.test.ts | 1 + packages/cli/src/setup-models.test.ts | 20 ++++++ packages/cli/src/setup-models.ts | 66 ++++++++++++++++++- .../agent/claude-agent-sdk-runner.service.ts | 1 - .../src/ingest/local-bundle-runtime.test.ts | 16 ++++- .../src/ingest/local-bundle-runtime.ts | 17 ++++- .../context/src/memory/local-memory.test.ts | 11 ++++ packages/context/src/memory/local-memory.ts | 20 ++++-- pnpm-lock.yaml | 3 + 16 files changed, 229 insertions(+), 21 deletions(-) diff --git a/docs-site/content/docs/concepts/the-context-layer.mdx b/docs-site/content/docs/concepts/the-context-layer.mdx index cb03b7c0..5b352fb1 100644 --- a/docs-site/content/docs/concepts/the-context-layer.mdx +++ b/docs-site/content/docs/concepts/the-context-layer.mdx @@ -250,6 +250,17 @@ canonical revenue reporting. Together, these four pillars give agents enough context to produce analytics artifacts that match what your team would produce - not just syntactically valid SQL, but the right query for the question. +## Agent runner backends + +KTX separates the global LLM provider from the agent runner. The global +provider powers non-agent calls such as scan enrichment and relationship +proposals. The agent runner powers curated tool loops used by ingest and memory +capture. + +Use `llm.agentRunner.backend: claude-code` when you want those curated loops to +run through the Claude Agent SDK. KTX registers only its stage-specific MCP +tools for the session and disables Claude Code built-in tools for that backend. + ## How KTX compares KTX is a context layer with an agent-native semantic layer at its core. MetricFlow, Cube, and Malloy model metrics, dimensions, joins, and generated SQL. KTX covers that semantic-layer work, then adds the context agents need to use and maintain it: wiki pages, schema scans, provenance, ingestion, validation, and agent-facing CLI commands. diff --git a/docs-site/content/docs/getting-started/quickstart.mdx b/docs-site/content/docs/getting-started/quickstart.mdx index 335aedfa..97e2f8e4 100644 --- a/docs-site/content/docs/getting-started/quickstart.mdx +++ b/docs-site/content/docs/getting-started/quickstart.mdx @@ -59,7 +59,7 @@ completed setup steps and resumes from the remaining work. KTX uses a Claude model for ingest agents that turn schemas, SQL, BI metadata, and documents into semantic-layer sources and wiki context. -Setup supports two LLM provider paths: +Setup supports two hosted LLM provider paths: | Provider | Use when | Credential model | |----------|----------|------------------| @@ -78,6 +78,28 @@ Setup checks the selected model before saving. Anthropic API setup fetches live Claude model choices when possible and falls back to bundled defaults if model discovery is unavailable. +### Use a local Claude Code session for ingest agents + +KTX can run ingest and memory-agent loops through your local Claude Code +session. This affects only agentic loops; scan enrichment, page triage, and +relationship proposals still use `llm.provider`. + +```bash +ktx setup --llm-backend claude-code --anthropic-model claude-sonnet-4-6 +``` + +The generated `ktx.yaml` uses: + +```yaml +llm: + provider: + backend: none + agentRunner: + backend: claude-code + models: + default: claude-sonnet-4-6 +``` + ## Step 3: Configure embeddings KTX uses embeddings for semantic search over semantic-layer sources, wiki diff --git a/packages/cli/package.json b/packages/cli/package.json index 539618f7..f2b81c15 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,6 +34,7 @@ "type-check": "tsc -p tsconfig.json --noEmit" }, "dependencies": { + "@anthropic-ai/claude-agent-sdk": "0.3.142", "@clack/prompts": "1.4.0", "@commander-js/extra-typings": "14.0.0", "@ktx/connector-bigquery": "workspace:*", diff --git a/packages/cli/src/commands/setup-commands.ts b/packages/cli/src/commands/setup-commands.ts index d09f8149..ab2f1ef3 100644 --- a/packages/cli/src/commands/setup-commands.ts +++ b/packages/cli/src/commands/setup-commands.ts @@ -29,7 +29,7 @@ function embeddingBackend(value: string): 'openai' | 'sentence-transformers' { } function llmBackend(value: string): KtxSetupLlmBackend { - if (value === 'anthropic' || value === 'vertex') { + if (value === 'anthropic' || value === 'vertex' || value === 'claude-code') { return value; } throw new InvalidArgumentError(`invalid choice '${value}'`); @@ -361,12 +361,18 @@ export function registerSetupCommands(program: Command, context: KtxCliCommandCo context.setExitCode(1); return; } - if (options.llmBackend === 'vertex' && (options.anthropicApiKeyEnv || options.anthropicApiKeyFile)) { + if ( + (options.llmBackend === 'vertex' || options.llmBackend === 'claude-code') && + (options.anthropicApiKeyEnv || options.anthropicApiKeyFile) + ) { context.io.stderr.write('Anthropic API key flags are only valid with --llm-backend anthropic.\n'); context.setExitCode(1); return; } - if (options.llmBackend === 'anthropic' && (options.vertexProject || options.vertexLocation)) { + if ( + (options.llmBackend === 'anthropic' || options.llmBackend === 'claude-code') && + (options.vertexProject || options.vertexLocation) + ) { context.io.stderr.write('Vertex AI flags are only valid with --llm-backend vertex.\n'); context.setExitCode(1); return; diff --git a/packages/cli/src/context-build-view.test.ts b/packages/cli/src/context-build-view.test.ts index 8d6b48ad..26a08c0c 100644 --- a/packages/cli/src/context-build-view.test.ts +++ b/packages/cli/src/context-build-view.test.ts @@ -913,6 +913,7 @@ describe('runContextBuild', () => { llm: { provider: { backend: 'gateway', gateway: { api_key: 'env:KTX_GATEWAY_API_KEY' } }, // pragma: allowlist secret models: { default: 'gpt-test' }, + agentRunner: { backend: 'ai-sdk' }, }, scan: { ...projectWithConnections({ warehouse: { driver: 'postgres' } }).config.scan, diff --git a/packages/cli/src/index.test.ts b/packages/cli/src/index.test.ts index 9c0a94fb..34b4f6db 100644 --- a/packages/cli/src/index.test.ts +++ b/packages/cli/src/index.test.ts @@ -1074,6 +1074,41 @@ describe('runKtxCli', () => { ); }); + it('dispatches Claude Code setup flags to the setup runner', async () => { + const setup = vi.fn(async () => 0); + const setupIo = makeIo(); + + await expect( + runKtxCli( + [ + '--project-dir', + tempDir, + 'setup', + '--no-input', + '--llm-backend', + 'claude-code', + '--anthropic-model', + 'claude-sonnet-4-6', + ], + setupIo.io, + { setup }, + ), + ).resolves.toBe(0); + + expect(setup).toHaveBeenCalledWith( + expect.objectContaining({ + command: 'run', + projectDir: tempDir, + inputMode: 'disabled', + cliVersion: '0.0.0-private', + llmBackend: 'claude-code', + anthropicModel: 'claude-sonnet-4-6', + skipLlm: false, + }), + setupIo.io, + ); + }); + it('rejects conflicting Anthropic credential setup flags', async () => { const setup = vi.fn(async () => 0); const setupIo = makeIo(); diff --git a/packages/cli/src/ingest.test-utils.ts b/packages/cli/src/ingest.test-utils.ts index 41affbb9..523c6e04 100644 --- a/packages/cli/src/ingest.test-utils.ts +++ b/packages/cli/src/ingest.test-utils.ts @@ -271,7 +271,7 @@ export class CliLookerSlWritingAgentRunner extends AgentRunnerService { verifiedIdentifiers: ['prod-warehouse', 'public.orders'], unverifiedIdentifiers: [], }, - { toolCallId: 'cli-looker-verification-ledger', messages: [] }, + { toolCallId: 'cli-looker-verification-ledger' }, ); const slWrite = params.toolSet.sl_write_source; if (!slWrite?.execute) { @@ -292,10 +292,13 @@ export class CliLookerSlWritingAgentRunner extends AgentRunnerService { measures: [{ name: 'total_revenue', expr: 'sum(revenue)' }], }, }, - { toolCallId: 'cli-looker-sl-write', messages: [] }, + { toolCallId: 'cli-looker-sl-write' }, ); - if (!result.structured.success) { - throw new Error(result.markdown); + const structured = + result && typeof result === 'object' && 'structured' in result ? result.structured : undefined; + if (!structured || typeof structured !== 'object' || !('success' in structured) || structured.success !== true) { + const message = result && typeof result === 'object' && 'markdown' in result ? result.markdown : String(result); + throw new Error(typeof message === 'string' ? message : 'sl_write_source failed'); } } return { stopReason: 'natural' as const }; diff --git a/packages/cli/src/setup-context.test.ts b/packages/cli/src/setup-context.test.ts index 61a9019a..c87a7cd6 100644 --- a/packages/cli/src/setup-context.test.ts +++ b/packages/cli/src/setup-context.test.ts @@ -61,6 +61,7 @@ async function writeReadyProject(projectDir: string, overrides: ReadyProjectOver llm: { provider: { backend: 'anthropic' }, models: { default: 'claude-sonnet-4-6' }, + agentRunner: { backend: 'ai-sdk' }, }, ingest: { ...defaults.ingest, diff --git a/packages/cli/src/setup-models.test.ts b/packages/cli/src/setup-models.test.ts index fc41cf1d..a9fb5355 100644 --- a/packages/cli/src/setup-models.test.ts +++ b/packages/cli/src/setup-models.test.ts @@ -975,6 +975,26 @@ describe('setup Anthropic model step', () => { expect(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')).not.toContain('completed_steps:'); }); + it('writes claude-code agent runner config when requested as the LLM backend', async () => { + const result = await runKtxSetupAnthropicModelStep( + { + projectDir: tempDir, + inputMode: 'disabled', + llmBackend: 'claude-code', + anthropicModel: 'claude-sonnet-4-6', + skipLlm: false, + }, + makeIo().io, + { claudeCodeAuthProbe: vi.fn(async () => ({ ok: true as const })) }, + ); + + expect(result.status).toBe('ready'); + const config = parseKtxProjectConfig(await readFile(join(tempDir, 'ktx.yaml'), 'utf-8')); + expect(config.llm.provider.backend).toBe('none'); + expect(config.llm.agentRunner.backend).toBe('claude-code'); + expect(config.llm.models.default).toBe('claude-sonnet-4-6'); + }); + it('returns back without writing config when Back is selected', async () => { const prompts = makePromptAdapter({ credentialChoice: 'back' }); const result = await runKtxSetupAnthropicModelStep( diff --git a/packages/cli/src/setup-models.ts b/packages/cli/src/setup-models.ts index 784a1d18..5246ec1b 100644 --- a/packages/cli/src/setup-models.ts +++ b/packages/cli/src/setup-models.ts @@ -53,7 +53,7 @@ export interface AnthropicModelChoice { recommended: boolean; } -export type KtxSetupLlmBackend = 'anthropic' | 'vertex'; +export type KtxSetupLlmBackend = 'anthropic' | 'vertex' | 'claude-code'; export interface KtxSetupModelPromptAdapter { select(options: { message: string; options: KtxSetupPromptOption[] }): Promise; @@ -68,6 +68,7 @@ export interface KtxSetupModelDeps { prompts?: KtxSetupModelPromptAdapter; listModels?: (apiKey: string) => Promise; healthCheck?: (config: KtxLlmConfig) => Promise; + claudeCodeAuthProbe?: () => Promise<{ ok: true } | { ok: false; message: string }>; readGcloudProject?: () => Promise; listGcloudProjects?: () => Promise; spinner?: () => KtxCliSpinner; @@ -238,6 +239,9 @@ export async function fetchAnthropicModels( } export function isKtxSetupLlmConfigReady(config: KtxProjectLlmConfig): boolean { + if (config.agentRunner?.backend === 'claude-code') { + return Boolean(config.models.default); + } let resolved: KtxLlmConfig | null; try { resolved = resolveLocalKtxLlmConfig(config, process.env); @@ -274,6 +278,7 @@ function buildProjectLlmConfig( }, models: { ...existing.models, default: model }, promptCaching: { ...(existing.promptCaching ?? {}), enabled: true, vertexFallbackTo5m: true }, + agentRunner: { backend: 'ai-sdk' }, }; } @@ -284,6 +289,7 @@ function buildProjectLlmConfig( }, models: { ...existing.models, default: model }, promptCaching: { ...(existing.promptCaching ?? {}), enabled: true }, + agentRunner: { backend: 'ai-sdk' }, }; } @@ -305,6 +311,30 @@ function buildVertexHealthConfig(vertex: { project?: string; location: string }, }; } +async function defaultClaudeCodeAuthProbe(): Promise<{ ok: true } | { ok: false; message: string }> { + const { query } = await import('@anthropic-ai/claude-agent-sdk'); + const session = query({ + prompt: '', + options: { + tools: [], + settingSources: [], + skills: [], + allowedTools: [], + disallowedTools: ['Bash', 'Read', 'Edit', 'Write', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Task'], + permissionMode: 'dontAsk', + maxTurns: 1, + }, + }); + try { + await session.accountInfo(); + return { ok: true }; + } catch (error) { + return { ok: false, message: error instanceof Error ? error.message : String(error) }; + } finally { + session.close(); + } +} + type LlmHealthProvider = 'Anthropic API' | 'Vertex AI'; function llmHealthCheckStartText(provider: LlmHealthProvider, model: string): string { @@ -483,13 +513,18 @@ async function chooseBackend( options: [ { value: 'anthropic', label: 'Anthropic API' }, { value: 'vertex', label: 'Google Vertex AI for Anthropic Claude' }, + { value: 'claude-code', label: 'Claude Code local session (agent runner only)' }, { value: 'back', label: 'Back' }, ], }); if (choice === 'back') { return { status: 'back' }; } - return { status: 'ready', backend: choice === 'vertex' ? 'vertex' : 'anthropic', prompted: true }; + return { + status: 'ready', + backend: choice === 'vertex' ? 'vertex' : choice === 'claude-code' ? 'claude-code' : 'anthropic', + prompted: true, + }; } function resolveProvidedVertexRef( @@ -826,6 +861,21 @@ async function persistLlmConfig( await markKtxSetupStateStepComplete(projectDir, 'llm'); } +async function persistClaudeCodeAgentRunnerConfig(projectDir: string, model: string): Promise { + const project = await loadKtxProject({ projectDir }); + const nextConfig: KtxProjectConfig = { + ...project.config, + llm: { + ...project.config.llm, + provider: { backend: 'none' }, + models: { ...project.config.llm.models, default: model }, + agentRunner: { backend: 'claude-code' }, + }, + }; + await writeFile(project.configPath, serializeKtxProjectConfig(nextConfig), 'utf-8'); + await markKtxSetupStateStepComplete(projectDir, 'llm'); +} + function buildInteractiveRetryArgs(args: KtxSetupModelArgs, backend?: KtxSetupLlmBackend): KtxSetupModelArgs { return { projectDir: args.projectDir, @@ -874,6 +924,18 @@ export async function runKtxSetupAnthropicModelStep( ? ({ ...attemptArgs, llmBackend: backendChoice.backend, showPromptInstructions: false } satisfies KtxSetupModelArgs) : attemptArgs; + if (backendChoice.backend === 'claude-code') { + const model = backendArgs.anthropicModel ?? 'claude-sonnet-4-6'; + const probe = await (deps.claudeCodeAuthProbe ?? defaultClaudeCodeAuthProbe)(); + if (!probe.ok) { + io.stderr.write(`Claude Code authentication check failed: ${probe.message}\n`); + return { status: 'failed', projectDir: args.projectDir }; + } + await persistClaudeCodeAgentRunnerConfig(args.projectDir, model); + io.stdout.write(`│ LLM ready: yes (Claude Code agent runner, ${model})\n`); + return { status: 'ready', projectDir: args.projectDir }; + } + if (backendChoice.backend === 'vertex') { const vertex = await chooseVertexConfig(backendArgs, io, deps); if (vertex.status === 'back' && backendChoice.prompted) { diff --git a/packages/context/src/agent/claude-agent-sdk-runner.service.ts b/packages/context/src/agent/claude-agent-sdk-runner.service.ts index 56544c65..8b3e1cbf 100644 --- a/packages/context/src/agent/claude-agent-sdk-runner.service.ts +++ b/packages/context/src/agent/claude-agent-sdk-runner.service.ts @@ -12,7 +12,6 @@ import { agentToolOutputToText, assertAgentToolSet, type AgentToolDefinition, - type AgentToolSet, } from './agent-tool.js'; import type { AgentRunnerPort, RunLoopParams, RunLoopResult, RunLoopStopReason } from './agent-runner.service.js'; diff --git a/packages/context/src/ingest/local-bundle-runtime.test.ts b/packages/context/src/ingest/local-bundle-runtime.test.ts index bee28653..be4d05a7 100644 --- a/packages/context/src/ingest/local-bundle-runtime.test.ts +++ b/packages/context/src/ingest/local-bundle-runtime.test.ts @@ -5,7 +5,7 @@ import { AgentRunnerService } from '../agent/index.js'; import { initKtxProject, type KtxLocalProject, loadKtxProject } from '../project/index.js'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { FakeSourceAdapter } from './adapters/fake/fake.adapter.js'; -import { createLocalBundleIngestRuntime } from './local-bundle-runtime.js'; +import { createLocalBundleIngestRuntime, resolveAgentRunnerForTest } from './local-bundle-runtime.js'; type RuntimeWithConnectionDeps = { deps: { @@ -55,7 +55,7 @@ describe('createLocalBundleIngestRuntime', () => { }), ).toThrow( [ - 'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner.', + 'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway; llm.agentRunner.backend: claude-code; or an injected agentRunner.', `Configure an Anthropic provider, then rerun ingest:`, ` ktx setup --project-dir ${project.projectDir} --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`, ].join('\n'), @@ -84,6 +84,18 @@ describe('createLocalBundleIngestRuntime', () => { await mkdir(runtime.storage.resolveUploadDir('job-1'), { recursive: true }); }); + it('constructs a Claude Agent SDK runner when llm.agentRunner.backend is claude-code', () => { + project.config.llm = { + provider: { backend: 'none' }, + models: { default: 'claude-sonnet-4-6' }, + agentRunner: { backend: 'claude-code' }, + }; + + const resolved = resolveAgentRunnerForTest({ project, adapters: [] }); + + expect(resolved.agentRunner.constructor.name).toBe('ClaudeAgentSdkRunnerService'); + }); + it('exposes canonical warehouse connection types to local ingest SL tools', async () => { project.config.connections.warehouse = { driver: 'postgres', diff --git a/packages/context/src/ingest/local-bundle-runtime.ts b/packages/context/src/ingest/local-bundle-runtime.ts index ddbf1b0c..dd6844e6 100644 --- a/packages/context/src/ingest/local-bundle-runtime.ts +++ b/packages/context/src/ingest/local-bundle-runtime.ts @@ -4,7 +4,7 @@ import { fileURLToPath } from 'node:url'; import type { KtxLlmProvider } from '@ktx/llm'; import YAML from 'yaml'; import type { AgentRunnerPort, AgentToolSet } from '../agent/index.js'; -import { AgentRunnerService as DefaultAgentRunnerService } from '../agent/index.js'; +import { AgentRunnerService as DefaultAgentRunnerService, ClaudeAgentSdkRunnerService } from '../agent/index.js'; import { localConnectionInfoFromConfig, type KtxSqlQueryExecutorPort } from '../connections/index.js'; import type { KtxEmbeddingPort, KtxLogger } from '../core/index.js'; import { noopLogger, SessionWorktreeService } from '../core/index.js'; @@ -570,7 +570,7 @@ function nextLocalJobId(): string { function localIngestLlmProviderGuardMessage(projectDir: string): string { return [ - 'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway, or an injected agentRunner.', + 'ktx ingest requires llm.provider.backend: anthropic, vertex, or gateway; llm.agentRunner.backend: claude-code; or an injected agentRunner.', 'Configure an Anthropic provider, then rerun ingest:', ` ktx setup --project-dir ${projectDir} --anthropic-api-key-env ANTHROPIC_API_KEY --anthropic-model claude-sonnet-4-6 --no-input`, ].join('\n'); @@ -587,6 +587,17 @@ function resolveAgentRunner(options: CreateLocalBundleIngestRuntimeOptions): { return { agentRunner: options.agentRunner, ...(llmProvider ? { llmProvider } : {}) }; } + if (options.project.config.llm.agentRunner.backend === 'claude-code') { + return { + agentRunner: new ClaudeAgentSdkRunnerService({ + projectDir: options.project.projectDir, + modelSlots: options.project.config.llm.models, + logger: options.logger ?? noopLogger, + }), + ...(llmProvider ? { llmProvider } : {}), + }; + } + if (!llmProvider) { throw new Error(localIngestLlmProviderGuardMessage(options.project.projectDir)); } @@ -603,6 +614,8 @@ function resolveAgentRunner(options: CreateLocalBundleIngestRuntimeOptions): { }; } +export const resolveAgentRunnerForTest = resolveAgentRunner; + export function createLocalBundleIngestRuntime( options: CreateLocalBundleIngestRuntimeOptions, ): LocalBundleIngestRuntime { diff --git a/packages/context/src/memory/local-memory.test.ts b/packages/context/src/memory/local-memory.test.ts index f0b870eb..0b022a84 100644 --- a/packages/context/src/memory/local-memory.test.ts +++ b/packages/context/src/memory/local-memory.test.ts @@ -143,6 +143,17 @@ describe('createLocalProjectMemoryCapture', () => { ); }); + it('constructs local memory capture with Claude Agent SDK runner config', async () => { + const project = await initKtxProject({ projectDir: tempDir }); + project.config.llm = { + provider: { backend: 'none' }, + models: { default: 'claude-sonnet-4-6' }, + agentRunner: { backend: 'claude-code' }, + }; + + expect(() => createLocalProjectMemoryCapture(project)).not.toThrow(); + }); + it('captures a semantic-layer source for a named local connection id', async () => { const project = await initKtxProject({ projectDir: tempDir }); project.config.connections.warehouse = { driver: 'postgres' }; diff --git a/packages/context/src/memory/local-memory.ts b/packages/context/src/memory/local-memory.ts index 4c15082c..03307b05 100644 --- a/packages/context/src/memory/local-memory.ts +++ b/packages/context/src/memory/local-memory.ts @@ -2,7 +2,7 @@ import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { KtxLlmProvider } from '@ktx/llm'; import YAML from 'yaml'; -import { AgentRunnerService, type AgentRunnerPort } from '../agent/index.js'; +import { AgentRunnerService, ClaudeAgentSdkRunnerService, type AgentRunnerPort } from '../agent/index.js'; import { localConnectionInfoFromConfig } from '../connections/index.js'; import type { KtxEmbeddingPort, KtxFileStorePort, KtxFileWriteResult } from '../core/index.js'; import { type KtxLogger, noopLogger, SessionWorktreeService } from '../core/index.js'; @@ -104,10 +104,16 @@ export function createLocalProjectMemoryCapture( }); const agentRunner = options.agentRunner ?? - new AgentRunnerService({ - llmProvider: requireLlmProvider(llmProvider), - logger, - }); + (project.config.llm.agentRunner.backend === 'claude-code' + ? new ClaudeAgentSdkRunnerService({ + projectDir: project.projectDir, + modelSlots: project.config.llm.models, + logger, + }) + : new AgentRunnerService({ + llmProvider: requireLlmProvider(llmProvider), + logger, + })); const memoryAgent = new MemoryAgentService({ settings: { knowledge: { userScopedKnowledgeEnabled: false }, @@ -145,7 +151,9 @@ export function createLocalProjectMemoryCapture( function requireLlmProvider(provider: KtxLlmProvider | null | undefined): KtxLlmProvider { if (!provider) { - throw new Error('createLocalProjectMemoryCapture requires llm.provider.backend or an injected agentRunner'); + throw new Error( + 'createLocalProjectMemoryCapture requires llm.provider.backend, llm.agentRunner.backend: claude-code, or an injected agentRunner', + ); } return provider; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd331dd4..77caf573 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,6 +73,9 @@ importers: packages/cli: dependencies: + '@anthropic-ai/claude-agent-sdk': + specifier: 0.3.142 + version: 0.3.142(zod@4.4.3) '@clack/prompts': specifier: 1.4.0 version: 1.4.0