feat(llm): add claude-code provider (#1193)#1200
feat(llm): add claude-code provider (#1193)#1200mvalentsev wants to merge 1 commit intoMemPalace:developfrom
Conversation
86ad3c9 to
724a556
Compare
|
Thanks for the thorough write-up, @mvalentsev, but unfortunately we can't merge this yet. The current Claude Code legal page (https://code.claude.com/docs/en/legal-and-compliance) reads:
Users with an Leaving the PR open so the discussion stays visible. Happy to revisit if Anthropic publishes guidance that permits this. |
|
@igorls fair concern. The frame I'm working from is OpenClaw's own published stance. Their Anthropic provider docs (https://docs.openclaw.ai/providers/anthropic) state, verbatim:
That's the most-watched third-party Anthropic harness, post-April-4 block (openclaw/openclaw#63316), publishing direct guidance from Anthropic that user-local This PR's OpenClaw's own docs do note that for long-lived gateway hosts, API keys remain "the clearest and most predictable production path" -- fair, and Your call. |
|
The usage from my perspective is different, agentic usage is sparse and with varied amount of tokens, which is in some ways similar to a human prompting an LLM, MemPalace using Haiku for example for entity extraction is a repeated mass automation usage. This is where I think the issue is. |
|
Hi, ClaudeCodeProvider.classify passes the full system prompt as a command-line argument ( Severity: action required | Category: security How to fix: Move system prompt off argv Agent prompt to fix - you can give this to your LLM of choice:
We noticed a couple of other issues in this PR as well - happy to share if helpful. Found by Qodo code review |
Adds a fourth LLM provider that routes through the local `claude` CLI binary using the user's Claude Pro/Max subscription via `claude auth login`. No API key needed; mirrors the existing ollama/openai-compat/anthropic provider shape (same `classify(system, user, json_mode)` and `check_available()` surface). Hooks into `get_provider()`; `mempalace init --llm --llm-provider claude-code` just works. Subprocess to `claude -p --output-format json --system-prompt ... --model ... --no-session-persistence`, run from `tempfile.gettempdir()` so claude does not pick up a project-level CLAUDE.md. `--bare` is intentionally omitted: it would force ANTHROPIC_API_KEY auth and disable OAuth / keychain, defeating the subscription path. Zero new pip dependencies. Subscription use from third-party harnesses is governed by Anthropic's policy and may be restricted later; `check_available()` surfaces auth errors at that point so callers can fall back.
724a556 to
22db326
Compare
Summary
Adds a
claude-codeLLM provider inmempalace/llm_client.pythat routes through the localclaudeCLI binary using the user's Claude Pro/Max subscription viaclaude auth login. Mirrors the existingOllamaProvider/OpenAICompatProvider/AnthropicProvidershape somempalace init --llm --llm-provider claude-code --llm-model claude-haiku-4-5works with no API key.Closes #1193.
How it works
Subprocess to:
User prompt on stdin;
cwd=tempfile.gettempdir()so claude does not pick up a project-levelCLAUDE.md. Auth flows throughclaude auth login(OAuth / keychain).check_available()runsclaude auth status --textand surfaces a friendly error pointing atclaude auth loginif not authenticated.Why subprocess and not
claude-agent-sdkThe Anthropic-published Python SDK was the obvious alternative; subprocess won on every axis for our use case:
subprocess.run(['claude', '-p', ...])claude-agent-sdkclaude-agent-sdk+anyio>=3.9>=3.10claude auth login(CLI keychain)The SDK is a thin wrapper around the same
claudebinary the user already has. Going direct keepsllm_client.py's zero-SDK style intact and does not raise the Python floor.Why
--bareis NOT usedclaude --barewould skip hooks, plugins, and CLAUDE.md auto-discovery for clean isolation. Fromclaude --help:That defeats the point of a subscription provider. We omit it and reduce ambient noise via
cwd=tempfile.gettempdir()and--no-session-persistenceinstead.Subscription policy fragility
This provider is fully opt-in:
--llmis opt-in, and within that--llm-provider claude-codeis opt-in. Defaultinitpath remains zero-API.Anthropic blocked OAuth-token-replay through third-party harnesses on April 4, 2026;
claude -pinvocation from third-party tools was subsequently sanctioned for first-party CLI binaries. That sanction may change. If it does,check_available()will return(False, ...)from the post-policyclaude auth statusfailure, surfacing a clear error before any classify call. Existingllm_refine.pycallers can fall through to a different provider.Documented in the provider docstring so future readers know this path is best-effort.
Failure modes
All raise
LLMErrorlike the other providers:claudebinary missing ->check_available()returns(False, "not found in PATH")check_available()returns(False, "Runclaude auth login...")claude -ptimeout ->LLMError("claude -ptimed out after Ns")OSError) ->LLMError("failed to spawn")LLMErrorwithstderr[:500]LLMError("non-JSON envelope")resultfield ->LLMError("empty result")Tests
13 new tests in
tests/test_llm_client.pymatching the existing per-provider function-based style: 1 factory-dispatch test, 11 unit tests coveringcheck_available()andclassify()paths (mockingsubprocess.runand/orshutil.which), and 1 gated integration testtest_claude_code_real_invocationthat runs a liveclaude -pround-trip whenMEMPAL_TEST_CLAUDE_CLI=1is set (skipped by default; CI has no authenticated user). Local pytest: 38 passed + 1 skipped, ruff clean.Out of scope
benchmarks/longmemeval_bench.py,benchmarks/locomo_bench.py)--llm-backend claude-code. Benchmarks are excluded from the package tests and have their own argparse layer (--llm-backend [anthropic, ollama]); plumbing claude-code through the rerank/refine call sites is a parallel concern. Happy to do it as a follow-up if useful.mempalace config set llm.provider ...persistence (planned under feat(init): optional LLM-assisted entity classification (phase 2) #1149 follow-ups).