Production Deployment & Monitoring
Production Considerations
Moving agent harnesses from development to production requires careful planning:
Deployment Architecture
Single-Agent Deployment
Multi-Agent Deployment
// Kubernetes deployment for multi-agent harness
const deployment = {
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: 'agent-harness',
labels: { app: 'ecommerce-agents' }
},
spec: {
replicas: 3,
selector: {
matchLabels: { app: 'ecommerce-agents' }
},
template: {
metadata: {
labels: { app: 'ecommerce-agents' }
},
spec: {
containers: [
{
name: 'planner-agent',
image: 'agents/planner:v1.0',
resources: {
requests: { memory: '512Mi', cpu: '500m' },
limits: { memory: '1Gi', cpu: '1000m' }
},
env: [
{ name: 'ANTHROPIC_API_KEY', valueFrom: { secretKeyRef: { name: 'api-keys', key: 'anthropic' } } },
{ name: 'AGENT_ROLE', value: 'planner' }
]
},
{
name: 'generator-agent',
image: 'agents/generator:v1.0',
resources: {
requests: { memory: '1Gi', cpu: '1000m' },
limits: { memory: '2Gi', cpu: '2000m' }
}
},
{
name: 'evaluator-agent',
image: 'agents/evaluator:v1.0',
resources: {
requests: { memory: '512Mi', cpu: '500m' },
limits: { memory: '1Gi', cpu: '1000m' }
}
}
],
volumes: [
{
name: 'shared-state',
persistentVolumeClaim: { claimName: 'agent-state-pvc' }
}
]
}
}
}
};
Managed Agents API
Anthropic's hosted solution for production deployments:
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
// Create a managed agent
const agent = await client.agents.create({
name: 'ecommerce-builder',
model: 'claude-sonnet-4-20250514',
// Define tools the agent can use
tools: [
{ type: 'filesystem', permissions: ['read', 'write'] },
{ type: 'bash', allowed_commands: ['npm', 'git', 'node'] },
{ type: 'browser', headless: true }
],
// Safety guardrails
guardrails: {
max_session_duration: '4h',
max_tokens_per_session: 1000000,
allowed_domains: ['localhost', '*.example.com'],
blocked_actions: ['delete_database', 'push_to_main']
},
// Cost controls
budget: {
max_cost_per_session: 10.00, // USD
alert_threshold: 5.00
}
});
// Start a session
const session = await client.agents.sessions.create({
agent_id: agent.id,
task: 'Build a product catalog page with filtering',
context: {
project_dir: '/workspace/ecommerce',
existing_code: await loadProjectContext()
}
});
// Stream session progress
for await (const event of session.stream()) {
switch (event.type) {
case 'tool_use':
console.log(`🔧 Tool: ${event.tool} - ${event.action}`);
break;
case 'message':
console.log(`💬 ${event.content}`);
break;
case 'checkpoint':
console.log(`📸 Checkpoint: ${event.description}`);
break;
case 'complete':
console.log(`✅ Session complete: ${event.result}`);
break;
case 'error':
console.log(`❌ Error: ${event.error}`);
break;
}
}
Monitoring & Observability
Structured Logging
import { Logger, AgentLogEvent } from '@anthropic-ai/agent-sdk';
const logger = new Logger({
service: 'ecommerce-agent',
level: 'info',
format: 'json',
destination: 'stdout' // For container logging
});
// Log agent events
agent.on('tool_use', (event) => {
logger.info('Agent tool use', {
sessionId: event.sessionId,
tool: event.tool,
action: event.action,
duration: event.duration,
success: event.success
});
});
agent.on('message', (event) => {
logger.info('Agent message', {
sessionId: event.sessionId,
role: event.role,
tokenCount: event.tokenCount
});
});
agent.on('error', (event) => {
logger.error('Agent error', {
sessionId: event.sessionId,
error: event.error.message,
stack: event.error.stack,
recoverable: event.recoverable
});
});
Metrics Collection
import { Metrics, Counter, Histogram, Gauge } from '@anthropic-ai/agent-sdk';
const metrics = new Metrics({
prefix: 'agent_harness',
labels: { service: 'ecommerce' }
});
// Define metrics
const sessionCounter = metrics.counter({
name: 'sessions_total',
help: 'Total number of agent sessions',
labels: ['status'] // 'success', 'failed', 'timeout'
});
const tokenHistogram = metrics.histogram({
name: 'tokens_used',
help: 'Tokens used per session',
buckets: [1000, 10000, 50000, 100000, 500000]
});
const activeSessionsGauge = metrics.gauge({
name: 'active_sessions',
help: 'Currently active agent sessions'
});
const costGauge = metrics.gauge({
name: 'session_cost_dollars',
help: 'Cost of current session in dollars'
});
// Record metrics
agent.on('session_start', () => {
activeSessionsGauge.inc();
});
agent.on('session_end', (event) => {
activeSessionsGauge.dec();
sessionCounter.inc({ status: event.status });
tokenHistogram.observe(event.tokensUsed);
costGauge.set(event.cost);
});
// Expose metrics endpoint
app.get('/metrics', async (req, res) => {
res.set('Content-Type', metrics.contentType);
res.end(await metrics.render());
});
Distributed Tracing
import { Tracer, SpanKind } from '@opentelemetry/api';
import { trace } from '@opentelemetry/api';
const tracer = trace.getTracer('agent-harness');
async function runAgentWithTracing(task: Task) {
return tracer.startActiveSpan('agent.run', async (span) => {
span.setAttributes({
'agent.task_id': task.id,
'agent.task_description': task.description
});
try {
// Tool use spans
agent.on('tool_use', (event) => {
const toolSpan = tracer.startSpan(`tool.${event.tool}`, {
kind: SpanKind.INTERNAL,
attributes: {
'tool.name': event.tool,
'tool.action': event.action
}
});
event.onComplete = () => {
toolSpan.setAttributes({
'tool.success': event.success,
'tool.duration_ms': event.duration
});
toolSpan.end();
};
});
// Run the agent
const result = await agent.run(task);
span.setAttributes({
'agent.status': result.status,
'agent.tokens_used': result.tokensUsed
});
return result;
} catch (error) {
span.recordException(error);
span.setStatus({ code: SpanStatusCode.ERROR });
throw error;
} finally {
span.end();
}
});
}
Safety Guardrails
Permission System
interface AgentPermissions {
filesystem: {
read: string[]; // Allowed read paths
write: string[]; // Allowed write paths
delete: string[]; // Allowed delete paths
};
network: {
allowedHosts: string[];
blockedHosts: string[];
maxRequestsPerMinute: number;
};
execution: {
allowedCommands: string[];
blockedCommands: string[];
maxExecutionTime: number;
};
api: {
maxTokensPerSession: number;
maxCostPerSession: number;
rateLimit: number;
};
}
const productionPermissions: AgentPermissions = {
filesystem: {
read: ['/workspace/**', '/config/**'],
write: ['/workspace/**'],
delete: ['/workspace/temp/**']
},
network: {
allowedHosts: ['api.anthropic.com', 'localhost', '*.internal.com'],
blockedHosts: ['*.malware.com'],
maxRequestsPerMinute: 100
},
execution: {
allowedCommands: ['npm', 'node', 'git', 'curl'],
blockedCommands: ['rm -rf', 'sudo', 'chmod 777'],
maxExecutionTime: 300000 // 5 minutes
},
api: {
maxTokensPerSession: 1000000,
maxCostPerSession: 10.00,
rateLimit: 10 // requests per second
}
};
Action Validation
class ActionValidator {
constructor(private permissions: AgentPermissions) {}
async validateAction(action: AgentAction): Promise<ValidationResult> {
const validators = [
this.validateFilesystem,
this.validateNetwork,
this.validateExecution,
this.validateCost
];
for (const validator of validators) {
const result = await validator.call(this, action);
if (!result.allowed) {
return result;
}
}
return { allowed: true };
}
private validateFilesystem(action: AgentAction): ValidationResult {
if (action.type !== 'filesystem') return { allowed: true };
const { operation, path } = action;
// Check if path is allowed
const allowedPaths = this.permissions.filesystem[operation];
const isAllowed = allowedPaths.some(pattern =>
minimatch(path, pattern)
);
if (!isAllowed) {
return {
allowed: false,
reason: `Filesystem ${operation} not allowed for path: ${path}`
};
}
return { allowed: true };
}
private validateExecution(action: AgentAction): ValidationResult {
if (action.type !== 'bash') return { allowed: true };
const { command } = action;
// Check for blocked commands
for (const blocked of this.permissions.execution.blockedCommands) {
if (command.includes(blocked)) {
return {
allowed: false,
reason: `Blocked command detected: ${blocked}`
};
}
}
// Check if command starts with allowed prefix
const commandBase = command.split(' ')[0];
if (!this.permissions.execution.allowedCommands.includes(commandBase)) {
return {
allowed: false,
reason: `Command not in allowlist: ${commandBase}`
};
}
return { allowed: true };
}
}
Circuit Breaker
class AgentCircuitBreaker {
private failures = 0;
private lastFailure: Date | null = null;
private state: 'closed' | 'open' | 'half-open' = 'closed';
constructor(
private threshold: number = 5,
private timeout: number = 60000
) {}
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.state === 'open') {
if (Date.now() - this.lastFailure!.getTime() > this.timeout) {
this.state = 'half-open';
} else {
throw new Error('Circuit breaker is open');
}
}
try {
const result = await fn();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
private onSuccess() {
this.failures = 0;
this.state = 'closed';
}
private onFailure() {
this.failures++;
this.lastFailure = new Date();
if (this.failures >= this.threshold) {
this.state = 'open';
console.log('🔴 Circuit breaker opened');
}
}
}
// Usage with agent
const breaker = new AgentCircuitBreaker(5, 60000);
async function runAgentSafely(task: Task) {
return breaker.execute(() => agent.run(task));
}
Cost Management
class CostTracker {
private sessionCosts: Map<string, number> = new Map();
constructor(
private maxCostPerSession: number = 10.00,
private alertThreshold: number = 5.00
) {}
trackTokens(sessionId: string, inputTokens: number, outputTokens: number) {
// Claude pricing (example rates)
const inputCost = inputTokens * 0.003 / 1000; // $3 per million input
const outputCost = outputTokens * 0.015 / 1000; // $15 per million output
const totalCost = inputCost + outputCost;
const currentCost = (this.sessionCosts.get(sessionId) || 0) + totalCost;
this.sessionCosts.set(sessionId, currentCost);
// Alert if approaching limit
if (currentCost >= this.alertThreshold && currentCost - totalCost < this.alertThreshold) {
console.log(`⚠️ Session ${sessionId} cost alert: ${currentCost.toFixed(2)}`);
}
// Enforce limit
if (currentCost >= this.maxCostPerSession) {
throw new Error(`Session ${sessionId} exceeded cost limit: ${currentCost.toFixed(2)}`);
}
return currentCost;
}
getSessionCost(sessionId: string): number {
return this.sessionCosts.get(sessionId) || 0;
}
}
Key Takeaways
- Production requires infrastructure - Logging, metrics, tracing
- Managed Agents simplifies deployment - Anthropic handles infrastructure
- Safety guardrails are essential - Permissions, validation, circuit breakers
- Monitor everything - Sessions, costs, errors, performance
- Plan for failure - Auto-recovery, checkpoints, graceful degradation
- Control costs - Token limits, budgets, alerts
Course Summary
Congratulations! You've completed the Agent Harnesses course. You now understand:
- Why harnesses matter - Bridging context windows for long-running work
- Architecture patterns - Two-agent and three-agent designs
- Session management - State persistence across sessions
- Communication - File-based protocols between agents
- Testing - Browser automation for verification
- Git integration - Version control for agents
- Claude Agent SDK - Building with Anthropic's official library
- Production deployment - Scaling, monitoring, safety
- Build your own agent harness for a specific domain
- Experiment with the Claude Agent SDK
- Explore Managed Agents for production deployments
- Join the Anthropic developer community
Happy building!