A claude agent sdk coding agent gives you programmatic control over Claude’s ability to read, write, and execute code inside a repository. In this tutorial we’ll stand up a minimal but real agent that scaffolds a Node.js service, runs its tests, and reports back—without the Claude Code CLI. You’ll need the official SDK, an API key, and about twenty minutes.
Prerequisites
- Node.js 20+ (the SDK uses native async iterators)
- An Anthropic API key exported as
ANTHROPIC_API_KEY - A scratch Git repo to experiment in
- TypeScript 5+ and
tsxfor running the script
Install the SDK:
npm init -y
npm install @anthropic-ai/claude-code zod
npm install -D tsx typescript
The @anthropic-ai/claude-code package exposes a query function that streams agent messages. That is the entire surface we need to build a claude agent sdk coding agent.
Step 1: A bare agent loop
Create agent.ts. The simplest possible run looks like this:
import { query } from "@anthropic-ai/claude-code";
async function run(prompt: string) {
for await (const msg of query({
prompt,
options: {
allowedTools: ["Read", "Write", "Bash"],
model: "claude-opus-4-20250514",
},
})) {
if (msg.type === "assistant") {
process.stdout.write(msg.message.content[0]?.text ?? "");
}
}
}
run("Create a file hello.ts that exports a function greeting(name: string).");
Run it:
npx tsx agent.ts
Expected output (truncated):
I'll create hello.ts.
[Write] hello.ts
File written.
The agent created hello.ts in the current directory. The allowedTools array restricts what it can do; omit Bash and it cannot run anything.
Step 2: Pin the working directory and system prompt
Blind file writes are dangerous. Constrain the agent to a sandbox/ folder and give it a persona. The SDK accepts systemPrompt and cwd:
import { mkdirSync } from "node:fs";
import { query } from "@anthropic-ai/claude-code";
mkdirSync("sandbox", { recursive: true });
async function run(task: string) {
for await (const msg of query({
prompt: task,
options: {
allowedTools: ["Read", "Write", "Bash"],
model: "claude-opus-4-20250514",
cwd: "sandbox",
systemPrompt: [
"You are a senior backend engineer.",
"Write only TypeScript. Never use any dependencies not in package.json.",
"After writing code, run `npm test` and fix failures.",
].join("\n"),
},
})) {
if (msg.type === "result") {
console.log("\nEXIT:", msg.subtype);
} else if (msg.type === "assistant") {
console.log(msg.message.content.map(c => c.text ?? "").join(""));
}
}
}
Now the claude agent sdk coding agent operates inside sandbox/ and self-verifies.
Step 3: Capture tool calls for logging
In a real system you want an audit trail. The stream emits tool_use and tool_result blocks inside assistant and user messages. Extract them:
function logToolUse(msg: any) {
for (const block of msg.message.content) {
if (block.type === "tool_use") {
console.log(`> tool=${block.name} input=${JSON.stringify(block.input)}`);
}
}
}
Wire it into the loop:
if (msg.type === "assistant") {
logToolUse(msg);
console.log(msg.message.content.map(c => c.text ?? "").join(""));
}
You’ll see lines like:
> tool=Write input={"file_path":"sandbox/index.ts","content":"..."}
> tool=Bash input={"command":"npm test"}
Step 4: Drive a concrete task
Let’s make the agent build a tiny HTTP server with one test. Pass a precise spec:
await run(`
Create package.json with typescript and vitest.
Create src/server.ts exporting start(port) that returns a http.Server on GET /health returning 200.
Create src/server.test.ts that requests /health and asserts status 200.
Run the test.
`);
The agent will:
- Write
package.json - Write
src/server.ts - Write
src/server.test.ts - Run
npm install && npm test - Loop on any type errors
Checkpoint output after a successful run:
> tool=Bash input={"command":"npm test"}
PASS src/server.test.ts
If you inspect sandbox/, the files exist and are runnable.
Step 5: Make the agent reusable as a module
Wrap the loop in a class so you can call it from a larger app:
import { query } from "@anthropic-ai/claude-code";
export class CodingAgent {
constructor(private cwd: string, private model = "claude-opus-4-20250514") {}
async execute(task: string): Promise<string> {
let last = "";
for await (const msg of query({
prompt: task,
options: {
allowedTools: ["Read", "Write", "Bash"],
model: this.model,
cwd: this.cwd,
systemPrompt: "You write TypeScript and verify with npm test.",
},
})) {
if (msg.type === "result") last = msg.subtype;
if (msg.type === "assistant")
last = msg.message.content.map(c => c.text ?? "").join("");
}
return last;
}
}
This claude agent sdk coding agent is now embeddable in a CI step or a chat backend.
Step 6: Routing through an inference gateway
The SDK points at Anthropic by default. If you want automatic fallback when a provider is rate-limited, you can proxy the underlying requests through an OpenAI-compatible gateway. n4n.ai exposes one endpoint that addresses 240+ models and honors client routing directives, but the Claude Agent SDK currently expects Anthropic’s wire format, so you’d need a shim that translates. For most teams, direct Anthropic access is simpler until you hit quota walls.
Step 7: Hardening for production
Three changes separate a toy from a tool:
- Timeout: wrap
executeinPromise.racewith a 120s guard. - Diffing: before
Write, have the agent rungit diffand return a summary block you parse. - Allowlist: never pass
Bashwith full shell; use a custom tool that only acceptsnpm testandtsc.
Example timeout:
async function withTimeout<T>(p: Promise<T>, ms: number): Promise<T> {
return Promise.race([
p,
new Promise<T>((_, rej) => setTimeout(() => rej(new Error("timeout")), ms)),
]);
}
Call withTimeout(agent.execute(task), 120_000).
What you have
You built a claude agent sdk coding agent that writes code, runs tests, and streams its reasoning. The core is ~40 lines. The SDK’s query function handles context compaction, tool orchestration, and result aggregation—you own the policy, not the plumbing.
Extend it with a retrieval tool for your internal docs, or a PR tool that opens a GitHub branch. The agent loop stays the same.