Anthropic Lost to the Elwood

How I scaled the AST techniques from my previous investigation into a full SDK replacement that calls agentLoop in-process — no subprocess spawning, no separate billing, 362 tests passing.

Repository: github.com/secemp9/elwood npm: @secemp/elwood


Yet Again, We Meet

Yet again, we see each other. Yes, you — provider-customer interest mismatch, they call it.

Anyway, if you haven't been living under a rock (or, you know, if you're as terminally online as I am), you've probably heard that Anthropic plans to fiddle with the usage of claude -p in general.

claude -p pricing Probably not disabling it outright, but incurring separate costs or something, I don't know the specifics.

Honestly, I get their agenda, and I'm not here to disparage them or litigate the decision. I thought I'd do something more productive with my time instead. You know, like scaling what I did in my last blog post and building a replacement with close parity to the official Anthropic SDK.

Enter elwood.


What I Did Then vs. Now

The way I envisioned this is pretty in-line with the previous blog. If you read that one you'd have the full context, but let me summarize the progression:

In the last post, I used Babel AST scripting to split and instrument a minified 11MB bundle — different AST-level tricks to deeply understand the UI freezing and performance issues, and provide concrete fixes that worked for me at the time on the version I was working with.

Along the way I also saw @dexh/shannon from my good friend Dex on twitter, which takes a different approach — using tmux to scrape Claude instances without claude -p at all.

However.

Another thought came to mind: "oh, but the official Anthropic SDK (@anthropic-ai/claude-agent-sdk) is just calling the installed Claude Code binary through subprocess and depending on IPC JSON streaming... but

we can scale this ...we can scale this."

Indeed we can. By using Babel AST to instrumentalize the entire stack that gets called and used by the official SDK, and by directly importing the functions, methods, and classes from the installed Claude Code "binary" (if you can even call it that — it's not compiled, it's just minified JavaScript, eh).

This doesn't just unlock faster usage since there's no subprocess overhead. You also unlock the plethora of everything else the Claude Code binary has to offer — the full agentLoop, the permission system, the MCP transport layer, session management, all of it. Running in-process.


How It Works

The official @anthropic-ai/claude-agent-sdk works like this: you call query(), it spawns claude -p as a child process, and then streams JSON back and forth over stdin/stdout via IPC. Your code talks to a subprocess. The subprocess talks to the API.

Elwood removes the subprocess entirely. Here's what happens when you call query():

  1. Locate the installed Claude Code CLI (cli.js — that 13MB bundle)
  2. Parse the entire bundle into an AST with Babel
  3. Fingerprint internal functions via evidence-based pattern matching (looking for agentLoop, appStateFactory, permissionChecker, etc.)
  4. Inject instrumentation hooks — additive, non-destructive, single-pass
  5. Import the instrumented bundle and call agentLoop() directly in-process

The instrumentation is key. Every injection is a conditional expression that no-ops when hooks aren't installed, so the bundle behaves identically to the original unless you wire up hooks. Original AST nodes are wrapped, never replaced.

The Code Comparison

Here's what the official SDK looks like:

import { query } from "@anthropic-ai/claude-agent-sdk";

for await (const message of query({
  prompt: "What files are in this directory?",
  options: { allowedTools: ["Bash", "Glob"], maxTurns: 3 }
})) {
  if ("result" in message) console.log(message.result);
}

Here's elwood:

import { query } from "@secemp/elwood";

for await (const message of query({
  prompt: "What files are in this directory?",
  options: { allowedTools: ["Bash", "Glob"], maxTurns: 3 }
})) {
  if ("result" in message) console.log(message.result);
}

Same API. Same for await loop. Same message shape. No subprocess. No separate API key needed if you're logged in via claude login (Pro/Max subscription works).

The full API surface is there — query(), tool(), createSdkMcpServer(), hooks (PreToolUse, PostToolUse, etc.), session resume/continue/fork, MCP servers (stdio, HTTP, SDK transport), subagents, permission modes, the V2 session API. All of it.


The Numbers

  • 362 tests passing across query parity, hooks, MCP behavioral tests, and gap-fix validations
  • 24 working examples — direct equivalents of every official SDK example (basic queries, hooks, MCP, custom tools, session stores, subagents, V2 sessions)
  • Close to 99% API parity with @anthropic-ai/claude-agent-sdk — I double checked but I might have missed something, any PRs/issues opened are welcome
  • Works with all auth methods: subscription (claude login), ANTHROPIC_API_KEY, OAuth, Bedrock, Vertex, Foundry

Install:

npm install @secemp/elwood

That's it. If Claude Code is installed globally, elwood finds it automatically.


Try me, Break me, Tell Me

I included a bunch of examples in the repo. If you find issues, or something doesn't work for your case, let me know. I intend on accepting PRs and issues as needed.


The Bigger Point

I want to be clear here. This isn't just me doing this out of frustration as usual (I do my best work when I'm frustrated or angry, for some reason). It's also a testament that the way they handled this is definitely questionable.

Not just because me or anyone else can circumvent it. But even if circumventing becomes harder, they need to understand that making Claude Code truly open source might be better long-term — just like OpenAI did with Codex. The competitive landscape is real. Cursor and Windsurf are growing, we have Gravity, OpenAI fully supports alternative agents through their subscription services. The moat was never in the tools.

Because at the end of the day, and as I said before:

"if it can be executed, then it can be parsed"

So it's mostly a cat and mouse game, and thus, a waste of time on both sides.


Previous post: Anthropic Doesn't Know How to Yield

Continue the thread

Browse the archive, read more context, or reach out directly.

All posts About Email