Sub-Agents, Hooks, and Skills: The Orchestration Layer Claude Code Won't Advertise
Sub-agents, hooks, and skills are Claude Code's secret orchestration layer. Patterns for multi-step automation without runaway token bills.
Sub-Agents: When to Fork Context vs Inline Tool Use
Understanding when to spawn a sub-agent versus using tools directly is fundamental to efficient agent design:
Use Sub-Agents When:
- Task decomposition benefits from isolation: Complex problems that can be cleanly split into independent sub-problems
- Different tool sets required: Sub-task needs access to tools the main agent shouldn't have (or vice versa)
- State isolation is valuable: You don't want sub-task pollution affecting main context (e.g., experimental approaches)
- Parallel execution possible: Sub-tasks can run simultaneously for performance gains
- Specialized expertise needed: Sub-agent can be pre-loaded with domain-specific skills or knowledge
- Retry semantics differ: Sub-task needs different error handling or retry policies than parent
Use Inline Tool Use When:
- Tight coupling with main context: Sub-task needs frequent access to current state or variables
- Low latency critical: Forking overhead would impact user experience
- Simple, linear operations: Straightforward sequences without branching complexity
- Resource conservation: Avoiding the overhead of additional agent instances
- Shared state essential: Multiple steps need to read/write the same variables
- Minimal contextual divergence: Sub-task doesn't need significantly different system prompts or tools
// Example: When to use sub-agents
// GOOD: Independent research tasks
const researchSubagent = await agent(
`Research best practices for ${topic} in SAP security`,
{
agentType: "research-specialist",
tools: ["web_search", "document_fetch"],
// Fresh context, no pollution from main task
}
);
// BETTER: Inline for tightly coupled operations
// Need to immediately use results in current calculation
const exchangeRate = await tool("fetch_exchange_rate", {
from: "USD",
to: "EUR"
});
const adjustedAmount = amount * exchangeRate;
Hooks: Pre/Post Tool Guards, Formatting, Policy Enforcement
Hooks are the invisible guardrails that make agent systems safe and predictable:
Types of Hooks and When to Use Them:
Pre-Tool Hooks (Guard Rails)
- Input validation: Check parameters before execution
- Permission checking: Verify user/role has rights
- Rate limiting: Prevent abuse or excessive usage
- Context enrichment: Add relevant information before processing
- Policy enforcement: Block actions violating corporate guidelines
- Transformation: Normalize inputs to expected format
Post-Tool Hooks (Quality Assurance)
- Result validation: Check output meets expectations
- Error handling: Convert failures to actionable feedback
- Data transformation: Convert output to needed format
- Logging/auditing: Record what happened for compliance
- State updates: Modify agent's internal state based on results
- Trigger chaining: Automatically start next step in workflow
Specialized Hook Types:
- Formatting hooks: Automatically apply code style (Prettier, ESLint fixes)
- Security scanning hooks: Check generated code for vulnerabilities
- Performance analysis hooks: Flag inefficient algorithms or patterns
- Compliance checking hooks: Ensure outputs meet regulatory requirements (SOX, GDPR, etc.)
Testing hooks: Automatically generate or run tests for generated code
// Example: Comprehensive hooks for SAP ABAP development
const abapHooks = {
// Prevent dangerous operations in production-like environments
preTool: async (toolCall) => {
const { name, arguments: args } = toolCall;
// Block direct production system modifications
if (name === 'execute_abap' &&
args.code.includes('UPDATE ') &&
process.env.TARGET_SYSTEM === 'PROD') {
throw new Error('Direct production updates require change management approval');
}
// Enforce naming conventions for new objects
if (name === 'create_abap_object' &&
!/^Z[A-Z][A-Z0-9_]*$/.test(args.objectName)) {
throw new Error('Custom objects must start with "Z" followed by uppercase letter');
}
return toolCall; // Allow if checks
},
// Ensure generated code follows team standards
postTool: async (toolResult) => {
if (toolResult.output && typeof toolResult.output === 'string') {
// Apply ABAP-specific formatting
const formatted = await formatAbapCode(toolResult.output);
// Check for hardcoded credentials (security)
if (/PASSWORD\s*=\s*['"][^'"]+['"]/i.test(formatted)) {
throw new Error('Hardcoded credentials detected - use secure vault instead');
}
// Verify proper error handling patterns
if (!/DATA.*EXCEPTION.*CX_/i.test(formatted) &&
/\b(CALL\s+FUNCTION|CALL\s+METHOD)/i.test(formatted)) {
console.warn('Consider adding exception handling for API calls');
}
return { ...toolResult, output: formatted };
}
return taskResult;
}
};
// Usage in agent configuration
const sapAgent = await agent(
"Refactor this legacy BAPI to follow clean core principles",
{
tools: ["read_abap", "write_abap", "execute_abap", "run_abap_unit_test"],
hooks: abapHooks
}
);
Skills: Reusable Domain Packs for SAP, Cloudflare, NAS
Skills package expertise, tools, and patterns into shareable units:
What Makes a Good Skill:
- Domain-focused: Clear boundaries (SAP ABAP, Cloudflare Workers, TrueNAS administration)
- Tool-curated: Includes exactly the tools needed for the domain — no more, no less
- Pre-configured: Comes with sensible defaults, templates, and examples
- Documented: Clear usage instructions and common patterns
- Versioned: Track improvements and breaking changes
- Composable: Works well with other skills (UNIX philosophy applied to agents)
Example: SAP ABAP Skill Components
Core Tools
- read_abap_source
- write_abap_source
- activate_transport
- run_abap_unit_test
- check_syntax
- execute_function_module
- read_table_sap
Initialized State
- Common naming conventions (Z*/*Y*)
- Transport layer configuration
- Standard authorization objects
- Preferred ABAP version flags
- Typical system parameters (client, language)
Prompt Templates
- "Explain this ABAP code for a junior developer"
- "Generate unit tests for this class/method"
- "Suggest performance improvements for this SELECT"
- "Convert this procedural code to object-oriented"
- "Find potential security issues in this report"
Workflow Patterns
- Test-driven development cycle
- Quick fix implementation process
- Transport request creation and management
- Performance analysis and optimization routine
- Security review checklist
Where to Find and Share Skills:
- Official registries: Claude Skills Marketway, Cursor Community
- Internal repositories: Company-wide skill libraries
- Open source: GitHub organizations sharing domain expertise
- Vendor-provided: SAP, Cloudflare, etc. offering official skills
- Peer sharing: Informal exchange between professional networks
Pro Tip: Create a "meta-skill" that combines your most frequently used domain skills. For example:
- SASTechnician = SAP ABAP Skill + Security Testing Skill + Performance Optimization Skill
- CloudDevOps = Cloudflare Workers Skill + Infrastructure as Code Skill + Monitoring & Alerting Skill
- FullStackDeveloper = Frontend Skill + Backend Skill + Database Skill + DevOps Skill
Comparison to n8n/Ollama Orchestration on Homelab
How Claude Code's approach compares to familiar orchestration tools:
Claude Code Native Orchestration
- Granularity: Fine-grained control at tool-call level
- Context sharing: Rich, structured state between steps
- Failure handling: Sophisticated retry/circuit breaker patterns
- Debugging: Step-by-step inspection of agent reasoning
- Language: Natural language + code
- Latency: Higher (LLM inference time)
- Best for: Cognitive tasks requiring judgment and adaptation
n8n/Ollama Homelab Approach
- Granularity: Coarse-grained (node-to-node)
- Context sharing: Limited (usually JSON payloads)
- Failure handling: Basic retry mechanisms
- Debugging: Visual execution tracing
- Language: Visual workflow + code nodes
- Latency: Lower (deterministic execution)
- Best for: Reliable, repetitive automation workflows
The Hybrid Sweet Spot:
Most sophisticated implementations combine both approaches:
- Use Claude Code for the "smart" parts requiring judgment (planning, interpretation, exception handling)
- Use n8n/Ollama workflows for the "reliable" parts (data movement, notifications, scheduled tasks)
- Connect them via webhooks, message queues, or shared databases
- Let each system do what it does best
Token Budgets and the "If Stuck Rule" for Agent Chains
Prevent runaway costs with these practical constraints:
Per-Turn Token Budgets:
- Set maximum tokens per agent interaction (include both prompt and completion)
- Typical values: 2K-4K for simple tasks, 8K-16K for complex reasoning
- Implement at the MCP/client level to catch violations early
- Provide graceful degradation when limits approached (summarize, ask clarifying questions)
- Log near-limit events for capacity planning
Conversation-Level Limits:
- Maximum turns per conversation before forced reset
- Successive failure limits (e.g., stop after 3 consecutive tool failures)
- Time-based expiration (auto-end conversations after N hours)
- Cost-based ceilings (stop if estimated cost exceeds threshold)
- User-initiated continuation options for legitimate long-running tasks
The "If Stuck Rule" - Essential for Production:
A practical heuristic for determining when to escalate or abort:
- Define what "stuck" means for your use case (no progress, looping, errors)
- Set a threshold (e.g., 3 attempts without meaningful advancement)
- When triggered:
- Escalate to human supervisor
- Fallback to simpler, more deterministic approach
- Return partial results with clear limitations
- Abort and return error with diagnostic information
// Example implementation of the "If Stuck Rule"
class AgentWithStuckDetection {
constructor(maxAttempts = 3) {
this.attempts = 0;
this.lastProgressCheck = Date.now();
this.lastKnownState = null;
}
async executeWithStuckProtection(operation) {
try {
// Check if we're stuck before attempting
if (this.isStuck()) {
throw new Error(`Agent appears stuck after ${this.attempts} attempts`);
}
this.attempts++;
const startState = await this.captureState();
// Attempt the operation
const result = await operation();
const endState = await this.captureState();
// Check if we made meaningful progress
if (this.hasMadeProgress(startState, endState)) {
this.resetStuckTracking();
return result;
}
// No meaningful progress - count as stuck attempt
throw new Error('No meaningful progress made');
} catch (error) {
// Handle stuck detection
if (error.message.includes('Stuck')) {
await this.handleStuckState();
throw error; // Re-throw after handling
}
throw error; // Re-throw other errors normally
}
}
// Implementation-specific methods would go here
isStuck() { /* ... */ }
captureState() { /* ... */ }
hasMadeProgress() { /* ... */ }
resetStuckTracking() { /* ... */ }
handleStuckState() { /* ... */ }
}
Security: What Sub-Agents Should Never Access
Define hard boundaries that no agent should ever cross:
Absolute Boundaries (Never Cross):
- Production credentials: Never store or transmit plaintext production passwords, keys, or certificates
- Unrestricted system access: No agent should have sudo/root equivalent without explicit justification
- Customer PII: Personally identifiable information requires special handling and consent
- Trade secrets: Clearly defined IP that requires additional protections
- Unauthorized networks: Attempting to pivot to or scan unauthorized network segments
- Policy violations: Actions explicitly forbidden by acceptable use policies
Conditional Boundaries (Context-Dependent):
- Development vs production: Access permissions should differ significantly between environments
- Data classification levels: Public, internal, confidential, restricted each need different handling
- Time-based access: Certain operations only allowed during maintenance windows
- Geographic restrictions: Data sovereignty requirements may limit where processing occurs
- Dual-person approval: High-risk operations requiring two authorized individuals
Implementing Defense in Depth:
- Network segmentation: Agents operate in isolated VLANs or security groups
- Principle of least privilege: Start with no permissions, grant only what's needed
- Regular permission reviews: Quarterly audits of agent access rights
- Automated anomaly detection: ML-based profiling of normal agent behavior
- Immutable audit logs: Cryptographically signed, append-only records of all agent actions
- Break-glass procedures: Clearly documented emergency access methods
- Regular penetration testing: Include agent systems in your red/blue team exercises
Remember: The most sophisticated agent in the world is worthless if it creates a security breach. Security isn't a feature — it's the foundation everything else builds upon.
Template: One Hooks File for ayraix Content Pipeline
Here's a concrete example of how to implement governance for a real-world content pipeline:
// .ayraix-content-hooks.js
// Governs AI-assisted content creation for ayraix.com
const crypto = require('crypto');
/**
* Generates a deterministic content ID for audit tracking
*/
function generateContentId(title, author) {
const hash = crypto.createHash('sha256');
hash.update(`${title}|${author}|${new Date().toISOString().slice(0,10)}`);
return hash.digest('hex').substring(0, 12);
}
/**
* Pre-hook: Validate content creation requests
*/
async function preContentCreationHook(request) {
const { type, title, author, metadata = {} } = request;
// 1. Authentication check
if (!await validateUserPermissions(author, 'content:create')) {
throw new Error(`User ${author} lacks content creation permissions`);
}
// 2. Input validation
if (!title || title.trim().length < 3) {
throw new Error('Title must be at least 3 characters');
}
if (title.length > 100) {
throw new Error('Title exceeds maximum length of 100 characters');
}
// 3. Duplicate prevention (basic)
if (await titleExistsRecently(title, author)) {
throw new Error('Similar title recently used by this author - consider variation');
}
// 4. Content type validation
const validTypes = ['article', 'tutorial', 'case_study', 'news', 'reference'];
if (!validTypes.includes(type)) {
throw new Error(`Invalid content type: ${type}. Must be one of: ${validTypes.join(', ')}`);
}
// 5. Add tracking metadata
return {
...request,
contentId: generateContentId(title, author),
timestamp: new Date().toISOString(),
version: 1
};
}
/**
* Post-hook: Validate and process generated content
*/
async function postContentGenerationHook(result) {
const { content, metadata, contentId } = result;
// 1. Basic integrity check
if (!content || content.trim().length === 0) {
throw new Error('Generated content is empty');
}
// 2. Length validation (adjusted for content type)
const minLengths = {
article: 800,
tutorial: 1200,
case_study: 1000,
news: 400,
reference: 500
};
const minLength = minLengths[metadata.type] || 500;
if (content.length < minLength) {
throw new Error(`Content too short: ${content.length} characters (minimum ${minLength} for ${metadata.type})`);
}
// 3. Quality checks
const qualityScore = await assessContentQuality(content, metadata.type);
if (qualityScore < 0.6) {
throw new Error(`Content quality below threshold: ${qualityScore}/1.0`);
}
// 4. SEO basics validation
const seoIssues = await validateBasicSeo(content, metadata);
if (seoIssues.length > 0) {
console.warn(`SEO recommendations: ${seoIssues.join(', ')}`);
// In production, might return these as warnings rather than blocking
}
// 5. Plagiarism check (simplified)
const originalityScore = await checkOriginality(content);
if (originalityScore < 0.85) {
throw new Error(`Originality concern: ${originalityScore}/1.0 - possible excessive similarity to existing content`);
}
// 6. Add final metadata for publishing
return {
...result,
wordCount: content.split(/\s+/).length,
readingTime: Math.ceil(content.split(/\s+/).length / 200), // 200 WPM
seoScore: await calculateSeoScore(content),
readyForReview: true,
reviewDueDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString() // 1 week
};
}
/**
* Helper: Check if user has permission for action
*/
async function validateUserPermissions(username, permission) {
// In reality, this would check against your auth system
// For demo: everyone can create content
return true;
}
/**
* Helper: Check for recent similar titles
*/
async function titleExistsRecently(title, author) {
// In reality, this would query your content database
// For demo: assume no recent duplicates
return false;
}
/**
* Helper: Assess content quality (simplified)
*/
async function assessContentQuality(content, type) {
// In reality, this might use ML models or rule-based checks
// For demo: basic heuristics
const score = {
hasIntroduction: content.includes('# ') || content.startsWith('# ') ? 0.2 : 0,
hasConclusion: content.toLowerCase().includes('conclusion') ||
content.toLowerCase().includes('summary') ? 0.2 : 0,
properLength: content.length > 300 ? 0.2 : 0,
hasExamples: content.includes('example') || content.includes('for instance') ? 0.2 : 0,
readability: /* Simplified */ 0.2
};
return Object.values(score).reduce((sum, val) => sum + val, 0);
}
/**
* Helper: Basic SEO validation (simplified)
*/
async function validateBasicSeo(content, metadata) {
const issues = [];
// Check for title in content
if (!content.includes(metadata.title)) {
issues.push('Title not found in body content');
}
// Check for sufficient headings
const headingCount = (content.match(/^#+/gm) || []).length;
if (headingCount < 2) {
issues.append('Consider adding more section headings');
}
// Check for alt text on images (if any)
const imgTags = content.match(/
]*>/g) || [];
const imgWithoutAlt = imgTags.filter(tag => !/alt\s*=/.test(tag));
if (imgWithoutAlt.length > 0) {
issues.push(`${imgWithoutAlt.length} images missing alt text`);
}
return issues;
}
/**
* Helper: Calculate SEO score (simplified)
*/
async function calculateSeoScore(content) {
// In reality, this would be more sophisticated
let score = 0.5; // Base score
// Bonus for good structure
if ((content.match(/^#+/gm) || []).length >= 3) {
score += 0.2;
}
// Bonus for reasonable length
if (content.length > 800 && content.length < 3000) {
score += 0.2;
}
// Penalty for keyword stuffing (simplified)
const wordCount = content.split(/\s+/).length;
const uniqueWords = new Set(content.toLowerCase().split(/\W+/)).size;
const repetitionRatio = 1 - (uniqueWords / Math.max(wordCount, 1));
if (repetitionRatio > 0.7) {
score -= 0.2;
}
return Math.min(1, Math.max(0, score));
}
/**
* Helper: Check originality (simplified placeholder)
*/
async function checkOriginality(content) {
// In reality, this would compare against existing content database
// For demo: assume reasonably original
return 0.9;
}
// Export for use in agent configuration
module.exports = {
preContentCreationHook,
postContentGenerationHook
};
This hooks file would be referenced in your agent configuration like:
const { preContentCreationHook, postContentGenerationHook } = require('./.ayraix-content-hooks');
const contentAgent = await agent(
"Write a technical article about MCP security best practices",
{
// Your content-specific tools
tools: [
"web_search",
"document_fetch",
"write_file",
"read_file",
"execute_command" // For running linters, etc.
],
// Apply our custom hooks
hooks: {
preTool: preContentCreationHook,
postTool: postContentGenerationHook
},
// Additional configuration
maxTurns: 10,
temperature: 0.7, // Balance creativity with coherence
system: `You are an expert technical writer for ayraix.com.
Follow the SAP-inspired writing style: practical,
skepticism-tempered, focused on real-world implementation.`
}
);
Building Reliable Agent Workflows: Putting It All Together
The most successful implementations combine these elements thoughtfully:
-
Start Simple: Begin with linear workflows before adding complexity
- Master single-agent tasks with good hooks
- Add skills for domain expertise
- Then experiment withsub-agent decomposition
-
Invest in Observability: You can't improve what you can't measure
- Log all tool invocations with inputs/outputs
- Track token usage and costs by workflow type
- Monitor success/failure rates and common failure points
- Implement distributed tracing for complex workflows
-
Test Rigorously: Treat agent workflows like any other critical software
- Unit tests for individual tools and hooks
- Integration tests for common workflows
- End-to-end tests for complete processes
- Chaos testing: simulate tool failures, timeouts, unexpected outputs
-
Document Thoroughly: Future-you (and teammates) will thank present-you
- Workflow purpose and business value
- Step-by-step execution logic
- Expected inputs, outputs, and side effects
- Known limitations and failure modes
- Runbooks for troubleshooting common issues
-
Iterate Based on Feedback: Continuous improvement beats perfectionism
- Regular retrospectives on what's working and what's not
- Metrics-driven adjustments to workflow design
- Stay updated on platform improvements and new capabilities
- Be willing to abandon approaches that consistently underperform
Remember the Goal:
The purpose of agent orchestration isn't to replace human judgment — it's to eliminate toil, reduce cognitive load, and create space for higher-value work. The best implementations make humans more effective, not obsolete.