Skip to content

Claude Agent SDK, Wizard polish#6

Merged
emanuilo merged 10 commits into
mainfrom
feat/claude-agent-sdk
May 20, 2026
Merged

Claude Agent SDK, Wizard polish#6
emanuilo merged 10 commits into
mainfrom
feat/claude-agent-sdk

Conversation

@emanuilo

@emanuilo emanuilo commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

Migrates the agent from raw Anthropic API calls to claude-agent-sdk,
adds a pluggable AgentBackend protocol so future SDKs (Cursor, OpenAI
Agents) can drop in without touching the agent core, and gives users two
ways to authenticate Claude: a subscription OAuth token or an Anthropic
API key.

This is Phase 1 of the harness-agnostic agent layer — only the Claude
backend ships; the protocol is in place for a separate Cursor-SDK PR.

Highlights

Agent backend abstraction

  • New memclaw/backends/ package: base.py defines AgentBackend +
    TurnResult, claude.py is the first implementation, __init__.py
    holds the registry.
  • MemclawAgent owns memory / search / history / consolidation / system
    prompt; every LLM call goes through backend.run_turn (with tools) or
    backend.run_one_shot (consolidation). No SDK imports remain in
    agent.py.
  • Backend is selected via the AGENT_BACKEND env var, defaulting to
    claude. The wizard auto-shows a selector panel once a second backend
    is registered.

Two-mode Claude auth

  • SubscriptionCLAUDE_CODE_OAUTH_TOKEN from claude setup-token.
    Billed against your Claude plan; the per-message cost line is omitted
    from logs.
  • API keyANTHROPIC_API_KEY (sk-ant-…). Pay-as-you-go; cost is
    logged per turn.
  • _build_env strips every Claude/Anthropic credential env var before
    injecting exactly one based on the chosen mode — a leftover token in
    the parent shell can't shadow the selection.

Wizard polish

  • ANSI Shadow wordmark banner ("mem" white, "claw" cyan).
  • Per-backend credential collection lives on the backend class
    (wizard_setup()).
  • Secret prompts use a cbreak-mode reader that echoes only the first 4
    characters verbatim and * after — pasted tokens don't sit in the
    scrollback in clear.
  • ALLOWED_USER_IDS is now required during memclaw telegram setup
    (without it the bot rejected every message — calling it optional was
    misleading).
  • Switching auth modes scrubs the now-unselected credential from both
    the saved .env and the live os.environ, so MemclawConfig can't
    re-read a stale value mid-run.

Telegram resilience

  • Register a PTB error handler so NetworkError / TimedOut from the
    polling socket (laptop sleep/wake, transient connectivity drops) logs
    a single warning line and lets PTB retry quietly, instead of dumping
    a full traceback every time.

Tests

  • tests/test_agent.py rewritten to exercise orchestration against a
    FakeBackend — no SDK dependency in agent-level tests.
  • tests/backends/test_claude.py covers Claude-specific behavior
    (auth-mode detection, env scrubbing, run_one_shot, run_turn) via a
    mock ClaudeSDKClient.
  • 103/103 passing.

Docs

  • README rewritten: badge → Claude Agent SDK; new "Claude authentication"
    subsection; variables table renamed to "Environment variables" and
    restructured around the OR between OAuth/API key; AGENT_BACKEND row
    added; pitch and Quick Start copy switched from "API keys" → "API
    tokens" to cover both OpenAI API keys and Claude OAuth tokens;
    filesystem-guardrail bullet rewritten (the path check now lives in the
    tool executor, not the SDK).

Migration / breaking notes

Existing users will need to re-run memclaw configure once. The wizard
now asks which auth mode they want and writes either
CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY — whichever wasn't
chosen is removed from .env. AGENT_BACKEND=claude is also written
explicitly (defaults preserve old behavior, but writing it makes the
choice visible).

The claude-agent-sdk dependency is capped to <0.2 — 0.1.x ships
breaking changes between minor versions.

emanuilo and others added 10 commits May 18, 2026 20:54
The wizard now asks which auth path the user wants and prompts only for the
chosen credential:

- Subscription (Pro/Max/Team): CLAUDE_CODE_OAUTH_TOKEN via `claude setup-token`.
  No per-message dollar figure is logged — usage is billed against the plan.
- API key (pay-as-you-go): ANTHROPIC_API_KEY. Cost is logged per turn.

config.auth_mode resolves to "subscription" / "api_key" / "" based on which
credential is configured. _build_env scrubs every Claude/Anthropic env var
from the subprocess env before injecting exactly the one matching the chosen
mode, so a leftover token from a prior shell can't override the selection.

When the wizard switches modes it also clears the dropped credential from
the running process's os.environ, otherwise MemclawConfig would re-read the
stale value through python-dotenv on the same run.

ALLOWED_USER_IDS is now required during `memclaw telegram` setup — without
it the bot rejects every message, so calling it optional was misleading.

Prompt order is now auth-mode panel → chosen Claude credential → OpenAI
(labelled with what it powers) → channel-specific keys. CLI auth gate is
factored into a single _require_claude_auth() helper. The dead _session_id
field on MemclawAgent is removed.
Served its purpose diagnosing the dotenv-reload bug fixed in the previous
commit. Kept on branch feat/claude-agent-sdk-debug-env in case it's needed
again.
- Rewrite agent.py module docstring to match the two-mode auth behavior
  (the WIP version claimed it always strips ANTHROPIC_API_KEY).
- Add SAFETY comment on permission_mode="bypassPermissions" naming the
  guardrails (allowed_tools + _BUILTIN_TOOLS_DISALLOW) it depends on.
- Migrate test_agent.py from mocking the removed anthropic._client to
  mocking ClaudeSDKClient via a helper that yields real AssistantMessage
  and ResultMessage so isinstance checks still hold.
- Cap claude-agent-sdk to <0.2 — 0.1.x ships breaking changes between
  minor versions.
…ocol

MemclawAgent now owns memory, search, history, consolidation, and the
system-prompt shape, but delegates every LLM call to an AgentBackend
(memclaw/backends/base.py). The Claude Agent SDK becomes one of N
possible backends instead of being hardwired into the agent.

Selection happens via the AGENT_BACKEND env var (config.agent_backend),
defaulting to "claude". The wizard auto-shows a selector panel as soon
as a second backend is registered; with only Claude available today it
stays silent.

Layout:
- memclaw/backends/base.py     AgentBackend protocol + TurnResult
- memclaw/backends/claude.py   Claude SDK guts, MCP wrapping, env scrubbing,
                               stream-json image protocol, auth-mode UX
- memclaw/backends/__init__.py REGISTRY + build_backend()

Each backend owns: credential prompts (wizard_setup), readiness check
(is_configured), env-var construction for its subprocess, native tool
plumbing, response parsing, usage/cost normalization. The protocol is
intentionally narrow — no SDK types leak into MemclawAgent.

Adding a backend (e.g. Cursor SDK) is now: implement the protocol, add
the class to REGISTRY, set AGENT_BACKEND=<name>. No changes needed in
agent.py, tools.py, store/index/search/reminders, or the bot handlers.

Tests are split: tests/test_agent.py exercises orchestration with a
FakeBackend; tests/backends/test_claude.py covers Claude-specific
behavior (auth mode, env scrubbing, run_one_shot, run_turn) using the
existing SDK mock helper.

Drive-by: cap claude-agent-sdk dependency takes effect in uv.lock
(0.2.x → 0.1.x).
- Badge: Anthropic API → Claude Agent SDK.
- Rewrite the Architecture intro and Agent Layer paragraph to describe
  claude-agent-sdk (which drives the claude CLI subprocess) instead of
  the raw Anthropic API + hand-rolled loop.
- Add a Configuration subsection that explains the two auth modes:
  Claude subscription (CLAUDE_CODE_OAUTH_TOKEN, no per-message cost) and
  Anthropic API key (ANTHROPIC_API_KEY, pay-as-you-go).
- Restructure the variables table: CLAUDE_CODE_OAUTH_TOKEN and
  ANTHROPIC_API_KEY are now "One of these two"; add AGENT_BACKEND
  (optional). Rename the section to "Environment variables" since the
  table also lists allowlists and selectors that aren't API keys.
- Filesystem-guardrail bullet: drop the stale "SDK-level callback"
  framing — the path check now lives in the tool executor.
- Acknowledgments: point at claude-agent-sdk for the runtime, Anthropic
  for Claude itself.
- Tighten pitch copy: "two API keys" / "your API keys" → "two API
  tokens" / "your API tokens" so the wording covers both an OpenAI API
  key and a Claude OAuth token.
Splits the figlet output into "mem" (white) and "claw" (cyan) halves and
assembles them with rich.Text so the color split mirrors the logo. The
two halves are pre-generated and pasted in as string constants — pyfiglet
is only needed to regenerate, not at runtime.

Banner sits between matching blank lines so it visually separates from
the prompt above and the welcome panel below.
Register an error handler that logs NetworkError/TimedOut as a single
warning line so PTB's polling loop can retry quietly instead of dumping
a full traceback when the socket dies during sleep.
Replace Prompt.ask with a custom cbreak-mode reader for the setup
wizard's secret fields so pasted API keys and tokens echo only their
first 4 characters in clear; everything after shows as '*'. Applies to
both the generic KEYS loop and the Claude backend's credential prompt.
@emanuilo emanuilo merged commit 67f02ea into main May 20, 2026
4 checks passed
@emanuilo emanuilo changed the title Feat/claude agent sdk Claude Agent SDK, Wizard polish May 20, 2026
@emanuilo emanuilo deleted the feat/claude-agent-sdk branch June 22, 2026 07:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant