Skip to main content

AI & Agentic Coding

AI coding assistants have fundamentally changed how software is written. This guide covers how to work effectively with AI agents โ€” from prompting strategies to building production AI systems.

What Are AI Coding Agents?โ€‹

An AI coding agent is an AI model that can:

  • Read and write code
  • Execute commands and tests
  • Browse documentation
  • Use tools (file search, web search, code execution)
  • Iterate autonomously on a task

Unlike a simple chatbot, an agent can take actions โ€” editing files, running tests, making API calls โ€” and reason about the results.

Current State of the Field (2025)โ€‹

ToolProviderBest For
Claude CodeAnthropicFull codebase understanding, complex refactors, agentic tasks
GitHub CopilotGitHub/OpenAIInline code completion in IDEs
CursorCursor AIAI-first IDE experience
DevinCognitionFully autonomous software engineering
Gemini Code AssistGoogleGoogle Cloud integration

Claude Code: The CLI Agentโ€‹

Claude Code is Anthropic's official CLI tool that runs in your terminal and has access to your entire codebase.

Getting Startedโ€‹

# Install Claude Code
npm install -g @anthropic-ai/claude-code

# Run in your project directory
cd your-project
claude

What Claude Code Can Doโ€‹

  • Understand your entire codebase โ€” reads all files, understands relationships
  • Edit files โ€” makes precise changes, not just suggestions
  • Run commands โ€” executes tests, builds, linters
  • Git operations โ€” commits, creates branches, PRs
  • Web search โ€” looks up documentation and APIs
  • Multi-step tasks โ€” works autonomously on complex features

Effective Prompting for Code Agentsโ€‹

The quality of your output depends heavily on how you communicate.

โœ… Good Promptsโ€‹

Refactor the authentication module in src/auth/ to use JWT tokens
instead of sessions. The current implementation is in src/auth/session.ts.
Make sure to:
1. Update the login endpoint to return a JWT
2. Add a token refresh endpoint
3. Update the middleware in src/middleware/auth.ts
4. Keep backward compatibility for 30 days (feature flag)
5. Add tests for the new endpoints
I'm getting this error when running `npm test`:
[paste error]
Look at the failing test in src/__tests__/user.test.ts and fix the
underlying issue in the code (not just the test).

โŒ Weak Promptsโ€‹

Fix my code.
Make it better.
Add authentication.

Key Principles for Agent Promptingโ€‹

  1. Be specific about scope โ€” Tell the agent exactly which files/functions to touch
  2. Define success criteria โ€” "Tests should pass", "The API should return X"
  3. Provide context โ€” Error messages, expected behavior, relevant code snippets
  4. Break down complex tasks โ€” Multiple focused prompts > one vague prompt
  5. Iterate โ€” Start with a small piece, verify it works, then expand

Prompt Engineering for Codeโ€‹

Zero-Shot vs Few-Shotโ€‹

Zero-shot: Just describe the task

Write a function that validates an email address.

Few-shot: Show examples

Write a function that validates common inputs.

Examples:
- validateEmail('a@b.com') โ†’ true
- validateEmail('not-an-email') โ†’ false
- validatePhone('+1-555-1234') โ†’ true
- validatePhone('abc') โ†’ false

Now implement both validation functions.

Few-shot prompting consistently produces better, more aligned outputs.

Chain-of-Thought Promptingโ€‹

Ask the model to reason step-by-step before writing code:

Before implementing this function, think through:
1. What edge cases might break this?
2. What's the time complexity of the naive approach?
3. Can we do better?

Then implement: a function to find the kth largest element in an array.

Structured Output Requestsโ€‹

Review this code and respond in this format:

## Bugs Found
[list any bugs]

## Security Issues
[list any security concerns]

## Performance Issues
[list any performance concerns]

## Suggested Improvements
[list improvements, with code examples]

Code to review:
[paste code]

Building with the Claude APIโ€‹

Setupโ€‹

npm install @anthropic-ai/sdk
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});

Basic Messageโ€‹

const message = await client.messages.create({
model: 'claude-opus-4-6',
max_tokens: 1024,
messages: [
{
role: 'user',
content: 'Explain closures in JavaScript with an example.',
},
],
});

console.log(message.content[0].text);

System Promptโ€‹

const message = await client.messages.create({
model: 'claude-opus-4-6',
max_tokens: 2048,
system: `You are an expert JavaScript teacher. You explain concepts clearly
with practical examples. Always include runnable code snippets.`,
messages: [
{ role: 'user', content: 'How does the event loop work?' },
],
});

Streaming Responsesโ€‹

const stream = await client.messages.stream({
model: 'claude-opus-4-6',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Write a React todo app.' }],
});

for await (const chunk of stream) {
if (chunk.type === 'content_block_delta') {
process.stdout.write(chunk.delta.text);
}
}

Tool Use (Function Calling)โ€‹

Tool use lets Claude call external functions โ€” search the web, query a database, execute code, etc.

const tools = [
{
name: 'search_code',
description: 'Search the codebase for a function or class name',
input_schema: {
type: 'object',
properties: {
query: {
type: 'string',
description: 'The search query (function name, class name, etc.)',
},
file_pattern: {
type: 'string',
description: 'Optional glob pattern like "src/**/*.ts"',
},
},
required: ['query'],
},
},
{
name: 'run_tests',
description: 'Run the test suite and return the results',
input_schema: {
type: 'object',
properties: {
test_file: {
type: 'string',
description: 'Optional specific test file to run',
},
},
required: [],
},
},
];

// Agentic loop
async function agentLoop(initialMessage) {
const messages = [{ role: 'user', content: initialMessage }];

while (true) {
const response = await client.messages.create({
model: 'claude-opus-4-6',
max_tokens: 4096,
tools,
messages,
});

messages.push({ role: 'assistant', content: response.content });

if (response.stop_reason === 'end_turn') {
// Claude is done โ€” extract final text response
return response.content.find(b => b.type === 'text')?.text;
}

if (response.stop_reason === 'tool_use') {
// Claude wants to use a tool
const toolResults = [];

for (const block of response.content) {
if (block.type !== 'tool_use') continue;

let result;
if (block.name === 'search_code') {
result = await searchCodebase(block.input.query, block.input.file_pattern);
} else if (block.name === 'run_tests') {
result = await runTestSuite(block.input.test_file);
}

toolResults.push({
type: 'tool_result',
tool_use_id: block.id,
content: JSON.stringify(result),
});
}

messages.push({ role: 'user', content: toolResults });
}
}
}

// Usage
const result = await agentLoop(
'Find all places where we make API calls without error handling, then fix them.'
);

Model Context Protocol (MCP)โ€‹

MCP is an open standard (created by Anthropic) that lets AI agents connect to any data source or tool using a unified protocol.

What MCP Doesโ€‹

[Claude] โ†โ†’ [MCP Client] โ†โ†’ [MCP Server] โ†โ†’ [Your Tool/Database/API]

MCP servers expose:

  • Tools โ€” actions Claude can take (run a query, send an email, create a file)
  • Resources โ€” data Claude can read (database contents, API responses, files)
  • Prompts โ€” pre-built prompt templates

Setting Up MCP with Claude Codeโ€‹

In your Claude Code settings (~/.claude/settings.json):

{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"POSTGRES_URL": "postgresql://localhost/mydb"
}
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "your-token"
}
},
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/path/to/your/project"
]
}
}
}
MCP ServerWhat It Does
@modelcontextprotocol/server-filesystemRead/write local files
@modelcontextprotocol/server-githubGitHub repos, issues, PRs
@modelcontextprotocol/server-postgresQuery PostgreSQL
@modelcontextprotocol/server-sqliteQuery SQLite
@modelcontextprotocol/server-brave-searchWeb search
@modelcontextprotocol/server-puppeteerBrowser automation

Building a Custom MCP Serverโ€‹

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

const server = new Server(
{ name: 'my-api-server', version: '1.0.0' },
{ capabilities: { tools: {} } }
);

// Register a tool
server.setRequestHandler('tools/list', async () => ({
tools: [
{
name: 'get_user',
description: 'Fetch a user by ID from the API',
inputSchema: {
type: 'object',
properties: {
userId: { type: 'string', description: 'The user ID' },
},
required: ['userId'],
},
},
],
}));

// Handle tool calls
server.setRequestHandler('tools/call', async (request) => {
if (request.params.name === 'get_user') {
const { userId } = request.params.arguments;
const user = await fetchUserFromAPI(userId);

return {
content: [{ type: 'text', text: JSON.stringify(user) }],
};
}
});

// Start the server
const transport = new StdioServerTransport();
await server.connect(transport);

Agentic Workflows: Patterns & Best Practicesโ€‹

Pattern 1: Plan โ†’ Execute โ†’ Verifyโ€‹

async function codeFeature(featureDescription) {
// Step 1: Plan
const plan = await claude.message(`
Create a detailed implementation plan for: ${featureDescription}

Format as:
1. Files to create/modify
2. Step-by-step implementation order
3. Test cases to add
4. Potential edge cases
`);

// Step 2: Execute each step
for (const step of parseSteps(plan)) {
await claude.message(`
Execute this step: ${step}
Context: ${featureDescription}
Previous work: ${getWorkSummary()}
`);
}

// Step 3: Verify
const testResult = await runTests();
if (!testResult.passed) {
await claude.message(`
Tests failed. Error: ${testResult.error}
Fix the issue without breaking other tests.
`);
}
}

Pattern 2: Parallel Sub-Agentsโ€‹

For large tasks, spawn multiple agents working in parallel:

async function codeReview(files) {
// Review multiple files in parallel
const reviews = await Promise.all(
files.map(file =>
claude.message(`
Review ${file.path} for:
- Bugs
- Security issues
- Performance problems
- Code style

Content:
${file.content}
`)
)
);

// Synthesize findings
return claude.message(`
Summarize these code reviews and prioritize issues:
${reviews.join('\n\n---\n\n')}
`);
}

Pattern 3: Human-in-the-Loopโ€‹

For risky operations, pause and confirm before proceeding:

async function deployment(environment) {
const plan = await generateDeploymentPlan(environment);

// Show plan to human
console.log('Deployment plan:', plan);
const confirmed = await promptUser('Proceed with deployment? (yes/no)');

if (confirmed === 'yes') {
await executeDeployment(plan);
}
}

AI-Assisted Interview Prepโ€‹

You can use AI to prepare for your own technical interviews:

Mock Coding Interviewโ€‹

I'm preparing for a technical interview. Act as a senior engineer
interviewer at a top tech company. Give me a medium-difficulty
LeetCode-style problem, then guide me through the solution if I
get stuck. Evaluate my approach and give feedback at the end.

Mock System Design Interviewโ€‹

Act as a system design interviewer. Give me a system design
question (e.g., "Design Uber"). Ask me clarifying questions as
I explain my design, push back on weak areas, and evaluate my
final design. Start the interview.

Code Review Practiceโ€‹

Here's a piece of code I wrote [paste code].
Review it as if you're a senior engineer in a code review.
Be direct about issues. Don't just say "good job."

Behavioral Interview Coachโ€‹

I'm practicing behavioral interviews for a senior engineer role.
Ask me 5 behavioral questions that companies like Google and Meta
commonly ask. After each answer, give me specific feedback using
the STAR framework and tell me what was strong and what needs work.

Resourcesโ€‹