đŸ”Ĩ 0
⭐ 0
Lesson 8 of 10 20 min +150 XP

Git Integration for Agent Workflows

Git as Agent Infrastructure

Git isn't just for version control - it's critical infrastructure for agent harnesses:

Git for Agents

Anthropic emphasizes:

> "Agents commit changes with descriptive messages and leverage git to revert problematic changes."

Git Patterns for Agents

Pattern 1: Atomic Commits

Each logical change gets its own commit:

class AgentGitManager {
  // Commit after each discrete change
  async commitChange(
    description: string,
    files?: string[]
  ): Promise<string> {
    // Stage specific files or all changes
    if (files) {
      for (const file of files) {
        await bash(`git add "${file}"`);
      }
    } else {
      await bash('git add .');
    }

    // Check if there's anything to commit
    const status = await bash('git status --porcelain');
    if (!status.trim()) {
      console.log('Nothing to commit');
      return '';
    }

    // Create descriptive commit message
    const message = this.formatCommitMessage(description);
    await bash(`git commit -m "${message}"`);

    const hash = await bash('git rev-parse --short HEAD');
    console.log(`📝 Committed: ${hash} - ${description}`);

    return hash.trim();
  }

  private formatCommitMessage(description: string): string {
    // Follow conventional commits
    const type = this.inferCommitType(description);
    return `${type}: ${description}`;
  }

  private inferCommitType(description: string): string {
    const lower = description.toLowerCase();
    if (lower.includes('add') || lower.includes('create')) return 'feat';
    if (lower.includes('fix') || lower.includes('bug')) return 'fix';
    if (lower.includes('update') || lower.includes('change')) return 'chore';
    if (lower.includes('test')) return 'test';
    if (lower.includes('doc')) return 'docs';
    return 'feat';
  }
}

Pattern 2: Checkpoint Commits

Create checkpoints at key moments:

async function implementFeatureWithCheckpoints(feature: Feature) {
  const git = new AgentGitManager();

  // Checkpoint: Starting feature
  await git.commitChange(`Start ${feature.name}`);

  // Implement steps
  for (let i = 0; i < feature.steps.length; i++) {
    const step = feature.steps[i];

    // Do the work
    await implementStep(step);

    // Checkpoint after each step
    await git.commitChange(`${feature.name}: ${step.description}`);

    // Run tests
    const testResult = await runTests();

    if (!testResult.passed) {
      // Rollback this step
      await git.rollbackLastCommit();
      console.log(`âš ī¸ Step ${i + 1} failed tests, rolled back`);

      // Try again with different approach
      await implementStepAlternative(step);
      await git.commitChange(`${feature.name}: ${step.description} (alt approach)`);
    }
  }

  // Final checkpoint
  await git.commitChange(`Complete ${feature.name}`);
}

Pattern 3: Feature Branches

Isolate work on feature branches:

class AgentBranchManager {
  async startFeature(featureId: string): Promise<void> {
    const branchName = `feature/${featureId}`;

    // Ensure we're on main and up to date
    await bash('git checkout main');
    await bash('git pull origin main');

    // Create and switch to feature branch
    await bash(`git checkout -b ${branchName}`);
    console.log(`đŸŒŋ Created branch: ${branchName}`);
  }

  async completeFeature(featureId: string): Promise<void> {
    const branchName = `feature/${featureId}`;

    // Ensure all changes committed
    const status = await bash('git status --porcelain');
    if (status.trim()) {
      throw new Error('Uncommitted changes exist');
    }

    // Merge to main
    await bash('git checkout main');
    await bash(`git merge ${branchName} --no-ff -m "Merge ${featureId}"`);

    // Delete feature branch
    await bash(`git branch -d ${branchName}`);

    console.log(`✅ Merged and cleaned up: ${branchName}`);
  }

  async abandonFeature(featureId: string): Promise<void> {
    const branchName = `feature/${featureId}`;

    // Switch to main
    await bash('git checkout main');

    // Force delete the branch
    await bash(`git branch -D ${branchName}`);

    console.log(`đŸ—‘ī¸ Abandoned branch: ${branchName}`);
  }
}

Rollback Strategies

Strategy 1: Soft Rollback (Last Commit)

async rollbackLastCommit(): Promise<void> {
  // Keep changes in working directory
  await bash('git reset --soft HEAD~1');
  console.log('â†Šī¸ Rolled back last commit (changes preserved)');
}

Strategy 2: Hard Rollback (Last Commit)

async hardRollbackLastCommit(): Promise<void> {
  // Discard all changes
  await bash('git reset --hard HEAD~1');
  console.log('â†Šī¸ Rolled back last commit (changes discarded)');
}

Strategy 3: Rollback to Specific Checkpoint

async rollbackToCheckpoint(commitHash: string): Promise<void> {
  // First, check how many commits we're reverting
  const log = await bash(`git log --oneline ${commitHash}..HEAD`);
  const commitsToRevert = log.trim().split('\n').length;

  console.log(`â†Šī¸ Rolling back ${commitsToRevert} commits to ${commitHash}`);

  // Hard reset to checkpoint
  await bash(`git reset --hard ${commitHash}`);
}

Strategy 4: Safe Rollback with Backup

async safeRollback(targetHash: string): Promise<string> {
  // Create backup branch first
  const backupBranch = `backup/${Date.now()}`;
  await bash(`git branch ${backupBranch}`);

  console.log(`đŸ“Ļ Created backup: ${backupBranch}`);

  // Now safe to rollback
  await bash(`git reset --hard ${targetHash}`);

  return backupBranch; // Return backup branch name for potential recovery
}

Using Git History for Context

Agents can read git history to understand project state:

class AgentGitContextReader {
  // Get recent work summary
  async getRecentWorkSummary(count: number = 10): Promise<string> {
    const log = await bash(
      `git log --oneline --no-decorate -${count}`
    );
    return log;
  }

  // Find when a file was last modified
  async getFileLastModified(filePath: string): Promise<GitCommit> {
    const log = await bash(
      `git log -1 --format="%H|%s|%ai" -- "${filePath}"`
    );
    const [hash, message, date] = log.split('|');
    return { hash, message, date };
  }

  // See what changed in last commit
  async getLastCommitChanges(): Promise<string[]> {
    const diff = await bash('git diff --name-only HEAD~1 HEAD');
    return diff.trim().split('\n').filter(Boolean);
  }

  // Find commits related to a feature
  async findFeatureCommits(featureName: string): Promise<string> {
    const log = await bash(
      `git log --oneline --grep="${featureName}"`
    );
    return log;
  }

  // Get diff for code review
  async getCommitDiff(commitHash: string): Promise<string> {
    return await bash(`git show ${commitHash} --stat`);
  }
}

E-Commerce Agent Git Workflow

async function ecommerceAgentWorkflow(feature: Feature) {
  const git = new AgentGitManager();
  const branch = new AgentBranchManager();
  const context = new AgentGitContextReader();

  // 1. Read current state from git
  const recentWork = await context.getRecentWorkSummary();
  console.log('📜 Recent commits:', recentWork);

  // 2. Start feature branch
  await branch.startFeature(feature.id);

  // 3. Implement with checkpoints
  try {
    for (const step of feature.steps) {
      // Checkpoint before risky operation
      const checkpoint = await git.commitChange(`checkpoint: before ${step.name}`);

      try {
        await implementStep(step);
        await git.commitChange(`${feature.name}: ${step.name}`);

        // Verify with tests
        const tests = await runTests();
        if (!tests.passed) {
          throw new Error(`Tests failed: ${tests.error}`);
        }

      } catch (error) {
        console.log(`❌ Step failed: ${error.message}`);

        // Rollback to checkpoint
        await git.rollbackToCheckpoint(checkpoint);

        // Try alternative approach
        console.log('🔄 Trying alternative approach...');
        await implementStepAlternative(step);
        await git.commitChange(`${feature.name}: ${step.name} (alternative)`);
      }
    }

    // 4. Complete feature
    await branch.completeFeature(feature.id);

  } catch (error) {
    console.log(`đŸšĢ Feature failed: ${error.message}`);

    // Abandon the feature branch
    await branch.abandonFeature(feature.id);

    throw error;
  }
}

Git Hooks for Agents

Use git hooks to enforce quality:

# .git/hooks/pre-commit
#!/bin/bash

# Run type checking
npm run typecheck
if [ $? -ne 0 ]; then
  echo "❌ Type check failed"
  exit 1
fi

# Run linting
npm run lint
if [ $? -ne 0 ]; then
  echo "❌ Lint failed"
  exit 1
fi

echo "✅ Pre-commit checks passed"
// Agent respects hooks
async commitWithHooks(message: string): Promise<boolean> {
  try {
    await bash('git add .');
    await bash(`git commit -m "${message}"`);
    return true;
  } catch (error) {
    if (error.message.includes('pre-commit')) {
      console.log('âš ī¸ Pre-commit hook failed, fixing issues...');

      // Auto-fix what we can
      await bash('npm run lint:fix');

      // Try again
      await bash('git add .');
      await bash(`git commit -m "${message}"`);
      return true;
    }
    throw error;
  }
}

Commit Message Convention

Agents should follow consistent commit conventions:

interface CommitMessage {
  type: 'feat' | 'fix' | 'chore' | 'test' | 'docs' | 'refactor';
  scope?: string;
  description: string;
  body?: string;
  footer?: string;
}

function formatCommitMessage(msg: CommitMessage): string {
  let result = `${msg.type}`;

  if (msg.scope) {
    result += `(${msg.scope})`;
  }

  result += `: ${msg.description}`;

  if (msg.body) {
    result += `\n\n${msg.body}`;
  }

  if (msg.footer) {
    result += `\n\n${msg.footer}`;
  }

  // Agent attribution
  result += '\n\nGenerated by AI Agent';

  return result;
}

// Example usage
const commit = formatCommitMessage({
  type: 'feat',
  scope: 'cart',
  description: 'add quantity update functionality',
  body: 'Users can now update item quantities directly in the cart.\nIncludes validation for stock limits.',
  footer: 'Closes #42'
});

Key Takeaways

  • Git is infrastructure - Not just version control for agents
  • Atomic commits enable rollbacks - One change per commit
  • Feature branches isolate experiments - Safe to fail
  • Checkpoints are safety nets - Commit before risky operations
  • Git history provides context - Agents can read their own work
  • Hooks enforce quality - Catch issues before commit

Practice Exercise

Design a git workflow for an agent building a REST API:

  • Define the branching strategy
  • Create checkpoint rules (when to commit)
  • Implement rollback decision logic
  • Write commit message templates
  • Add a pre-commit hook for API validation

Next Lesson

Now let's explore Building with Claude Agent SDK - Anthropic's official library for creating production agent harnesses.

Testing with Browser Automation