← Back to list

Claude Code Agents: Give Your AI a Junior Engineer

Part 3 of the Claude Code series — how to build autonomous subagents that handle entire multi-step workflows end-to-end, with their own…

Shashi Kant · 2026-04-15 17:14 · 0 claps · 17.2 min read
#claude #claude-agent #software-engineering #leanring-journeys #claude-skills
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents

Claude Code Agents: Give Your AI a Junior Engineer

Part 3 of the Claude Code series — how to build autonomous subagents that handle entire multi-step workflows end-to-end, with their own context, tools, and decision logic.

In Part 1, Skills gave Claude your team’s patterns — sealed event hierarchies, manual-ack Kafka consumers, zero-downtime Flyway migrations. In Part 2, Hooks made it structurally impossible to violate those patterns — formatting, secret scanning, and checkstyle running automatically around every action.

That’s three layers of a four-layer architecture:

L1 — CLAUDE.md     Project memory — loads automatically every session
L2 — Skills        Correct patterns generated per task
L3 — Hooks         Standards enforced around every action
L4 — Agents        ← today

Today is L4. And it’s the one that changes what you think AI can actually do.

What Skills, Hooks, and Agents Each Solve

Before I explain Agents, it’s worth being precise about where Skills and Hooks stop.

A Skill answers: how should Claude write this specific type of code? A Hook answers: what must happen around every action Claude takes? An Agent answers: who should handle this entire multi-step workflow while I’m doing something else?

Skills and Hooks are reactive — they shape what happens when you ask Claude to do something. Agents are proactive — they are the something doing a complete task autonomously, making decisions, reading real files, running real commands, and producing a finished result.

The mental model that clicked for me:

Skills and Hooks make Claude a better pair programmer. Agents make Claude a junior engineer you can delegate a ticket to.

What an Agent Actually Is

An Agent in Claude Code is a subagent — a separate Claude instance spawned with its own:

  • Scoped CLAUDE.md — a focused instruction file in the agent’s directory
  • Tool access — bash, file reads and writes, web search
  • Working context — isolated from your main session
  • Decision loop — reads inputs, takes actions, evaluates results, repeats until done

You invoke an agent from your main Claude session with a natural language request. The agent runs the full workflow and hands back a result. Your main session continues with other work.

The key difference from a Skill: a Skill is a template Claude follows when generating code. An Agent is a worker that reads your actual codebase, analyses it, makes decisions, and produces real output — Flyway migration scripts, PR review comments, test reports.

The Agent Directory Structure

Agents live in .claude/agents/. Each agent gets its own subdirectory with:

  • AGENT.md — the agent's scoped instructions (like a CLAUDE.md but focused)
  • Optionally, helper scripts the agent can call
.claude/
├── settings.json
├── skills/
│   └── ...
└── agents/
    ├── db-migration-agent/
    │   └── AGENT.md
    ├── pr-review-agent/
    │   └── AGENT.md
    ├── kafka-topology-agent/
    │   └── AGENT.md
    └── test-gap-agent/
        └── AGENT.md

Commit everything. Every developer gets every agent automatically.

Agent 1: db-migration-agent

This is the one I teased in Part 2. The problem it solves:

Every time you add a field to a JPA entity, you need a Flyway migration. The migration needs to be versioned correctly (next in sequence), use CREATE INDEX CONCURRENTLY, add NOT NULL safely with a default, and never lock the table. Getting all of that right every time takes focus and knowledge of the current schema state.

The db-migration-agent reads your entity class, reads the existing migration files to find the next version number, reads the current table structure, and generates the complete, correct, zero-downtime migration SQL.

The AGENT.md

---
name: db-migration-agent
description: >
  Analyses a JPA entity class and the existing Flyway migration history to
  generate the correct next zero-downtime migration script. Invoke with the
  entity class path. Produces a ready-to-commit V{n}__description.sql file
  in src/main/resources/db/migration/ with correct versioning, safe NULL
  handling, and CONCURRENTLY indexes.
---

# DB Migration Agent
## Your job
Generate a complete, correct, production-safe Flyway migration for the
Java 21 + Spring Boot + PostgreSQL Order Service.

## Step 1 - Read the entity
Read the entity class provided. Extract:
- Table name from @Table(name = "...")
- All @Column fields: name, type, nullable, length, precision
- All @Index definitions
- All @OneToMany / @ManyToOne relationships (= FK constraints)
- Any new fields not yet in the migration history (these need migration)

## Step 2 - Read migration history
Run: ls src/main/resources/db/migration | sort -V
Find the highest V{n} version number.
Next migration = V{n+1}.
Read the most recent migration to understand the current table state.

## Step 3 - Diff: entity vs current schema
Compare the entity fields against what the latest migration created.
Fields in the entity but NOT in the migration history = changes to migrate.

## Step 4 - Generate migration SQL
Apply these rules without exception:
### Adding a nullable column (safe, zero-downtime)
ALTER TABLE {table} ADD COLUMN {col} {PG_TYPE};
-- No NOT NULL - add in a subsequent migration after backfill
### Adding NOT NULL with a safe default (PostgreSQL 11+ metadata-only)
ALTER TABLE {table} ADD COLUMN {col} {PG_TYPE} NOT NULL DEFAULT {value};
### Adding a NOT NULL column without a natural default (two-step)
-- Step 1: add nullable
ALTER TABLE {table} ADD COLUMN {col} {PG_TYPE};
-- Step 2: backfill
UPDATE {table} SET {col} = {backfill_value} WHERE {col} IS NULL;
-- Step 3: constrain
ALTER TABLE {table} ALTER COLUMN {col} SET NOT NULL;

### Adding an index (ALWAYS CONCURRENTLY - no table lock)
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_{table}_{col}
    ON {table}({col});
### Adding a FK constraint (NOT VALID for existing data)
ALTER TABLE {table}
    ADD CONSTRAINT fk_{table}_{parent}
    FOREIGN KEY ({col}) REFERENCES {parent_table}(id) NOT VALID;
ALTER TABLE {table} VALIDATE CONSTRAINT fk_{table}_{parent};
### Renaming a column (3-step - never in one migration)
-- Only generate Step 1 (add new column + copy data).
-- Note in a comment that Steps 2 and 3 require separate code deploy + migration.

## Step 5 - Write the file
Write to: src/main/resources/db/migration/V{NEXT}__{description}.sql
Description: snake_case, describes what changed, e.g. add_orders_priority_column
## Step 6 - Verify
Run: ./mvnw flyway:info
Confirm the new migration appears as "Pending" with the correct version.
Report the output to the user.
## Output to user
- File path written
- Version number assigned
- Summary of changes made
- Any manual steps required (e.g. code deploy between rename steps)
- Result of flyway:info
# Start your Claude Code session
claude

# Invoke the agent
> Run the db-migration-agent on src/main/java/com/example/orders/domain/OrderEntity.java

Claude spawns the agent. The agent:

  1. Reads OrderEntity.java
  2. Lists src/main/resources/db/migration/ — finds V1__initial_schema.sql is latest
  3. Reads V1__initial_schema.sql — sees orders table has id, customer_id, status, total, created_at, updated_at
  4. Compares to entity — sees notes TEXT column and priority INT field are new
  5. Generates V2__add_orders_notes_and_priority.sql:
-- V2__add_orders_notes_and_priority.sql
-- Generated by db-migration-agent
-- Entity: OrderEntity
-- New fields: notes (nullable TEXT), priority (NOT NULL INT DEFAULT 0)

ALTER TABLE orders ADD COLUMN notes TEXT;

ALTER TABLE orders ADD COLUMN priority INT NOT NULL DEFAULT 0;

CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_orders_priority
    ON orders(priority);
  1. Runs ./mvnw flyway:info and reports:
V1 — initial_schema       — Success
V2 — add_orders_notes_and_priority — Pending   ← your new migration

Total time: 45 seconds. Zero chance of getting the version number wrong, forgetting CONCURRENTLY, or accidentally using NOT NULL without a default on existing data.

Agent 2: pr-review-agent

The problem: PR review is expensive attention. Before a human spends 30 minutes on a review, every structural issue — wrong status codes, entity leaking from a controller, missing @Valid, consumer without a DLT listener — should already be caught.

The pr-review-agent reads every file changed in a diff, checks each one against your Skills, and produces a structured review report before the PR is even opened.

The AGENT.md

---
name: pr-review-agent
description: >
  Reviews every file changed in the current git diff against this project's
  non-negotiables and Skills. Produces a structured report with Blockers,
  Suggestions, and Passed checks. Invoke before opening any PR. Saves human
  reviewers from catching structural issues — they focus on logic instead.
---# PR Review Agent

## Your job
Perform a pre-PR structural review of all changed files in this Order Service.
Check every file against the project non-negotiables from CLAUDE.md and the
relevant Skills. Produce a structured report.

## Step 1 - Get the diff
Run: git diff main...HEAD --name-only
This lists every file changed on the current branch vs main.
Run: git diff main...HEAD --stat
This gives the change summary.

## Step 2 - Read each changed file
For every .java file in the diff, read its full content.
For every .sql file in the diff, read its full content.
For every .yml file changed, read its full content.

## Step 3 - Apply checks by file type
### For every @RestController class
- [ ] Only Record DTOs in/out - no @Entity class in any return type
- [ ] @Valid on every @RequestBody parameter
- [ ] @Operation and @ApiResponses on every endpoint method
- [ ] @ResponseStatus(CREATED) on POST methods
- [ ] @ResponseStatus(NO_CONTENT) on DELETE methods
- [ ] @PageableDefault on list endpoints
- [ ] Constructor injection - no @Autowired field injection
### For every @Service class
- [ ] Class annotated with @Transactional
- [ ] @Transactional(readOnly = true) on every read method
- [ ] ResourceNotFoundException thrown for missing resources - never null returned
- [ ] eventProducer.publish() called AFTER repository.save() - never before
- [ ] No direct calls to repository from within a @KafkaListener method
### For every @KafkaListener class
- [ ] MANUAL ack mode - ackMode = "MANUAL_IMMEDIATE"
- [ ] Idempotency guard present: processedEventRepository.existsById(event.eventId())
- [ ] Exception rethrown on failure (not swallowed)
- [ ] DLT @KafkaListener present in same class
- [ ] Consumer group ID follows {service}-{purpose} naming convention
- [ ] Java 21 sealed switch used for event routing - no instanceof chains
### For every @Entity class
- [ ] @Column(name = "snake_case") on every field
- [ ] @Enumerated(EnumType.STRING) on every enum field
- [ ] FetchType.LAZY on all relationships (EAGER requires an explaining comment)
- [ ] No business logic in entity methods
### For every Flyway .sql migration
- [ ] Version number is next in sequence (no gap, no reuse)
- [ ] CREATE INDEX uses CONCURRENTLY IF NOT EXISTS
- [ ] NOT NULL columns have DEFAULT or preceded by a backfill
- [ ] FK constraints use NOT VALID on tables with existing data
### For application.yml changes
- [ ] ddl-auto is still "validate" - not create, update, or create-drop
- [ ] open-in-view is still false
- [ ] No hardcoded secrets - all values use ${ENV_VAR:} references
- [ ] acks still "all" and enable.idempotence still true

## Step 4 - Check test coverage
For every new or changed @Service, @RestController, or @KafkaListener:
- Is there a corresponding *Test.java or *IT.java file in the diff?
- If not, flag as a suggestion (missing test coverage)

## Step 5 - Produce the report
Output in this exact format:
---
## PR Review Report
**Branch**: {branch name}
**Files changed**: {n}
**Reviewed by**: db-migration-agent
**Date**: {date}
### 🔴 Blockers (must fix before merge)
| File | Line | Issue | Fix |
|------|------|-------|-----|
| {file} | {line} | {issue} | {specific fix} |
### 🟡 Suggestions (non-blocking)
| File | Issue | Suggestion |
|------|-------|------------|
| {file} | {issue} | {suggestion} |
### 🟢 Passed
- [x] {check that passed}
- [x] {check that passed}
### 📋 Summary
{2-3 sentence overall assessment}
**Recommendation**: Approve / Approve with suggestions / Request changes
---
## Important rules
- Be specific: always include file name and the exact line/method causing the issue
- Never flag things that are intentional and documented (e.g. EAGER fetch with a comment)
- If a check cannot be verified from the diff alone, note it as "unable to verify from diff"
- Missing tests are Suggestions, not Blockers, unless the changed code is a critical path

Invoking the agent

# In your Claude Code session, after finishing your feature
> Run the pr-review-agent on my current branch before I open the PR

Here’s a real example output for a branch that added an Order status update endpoint:

## PR Review Report
Branch: feat/order-status-update
Files changed: 6
Reviewed by: pr-review-agent
Date: 2026-03-20

### 🔴 Blockers (must fix before merge)
| File | Line | Issue | Fix |
|------|------|-------|-----|
| OrderController.java | 47 | @RequestBody on updateStatus() is missing @Valid | Add @Valid annotation |
| OrderEventConsumer.java | 83 | Exception caught and logged but not rethrown | Rethrow so DefaultErrorHandler can retry and route to DLT |
| V2__update_order_status.sql | 8 | CREATE INDEX missing CONCURRENTLY | Use: CREATE INDEX CONCURRENTLY IF NOT EXISTS |
### 🟡 Suggestions (non-blocking)
| File | Issue | Suggestion |
|------|-------|------------|
| OrderServiceImpl.java | updateStatus() has no @Transactional(readOnly=...) - verify it's a write | Confirm @Transactional(readOnly=false) is intentional |
| OrderControllerTest.java | No test for 422 response on invalid status transition | Add test: PUT with invalid status → expect 422 |
### 🟢 Passed
- [x] All controllers return Record DTOs - no @Entity classes exposed
- [x] @Operation and @ApiResponses on all 3 new endpoints
- [x] @Transactional on OrderServiceImpl class level
- [x] ResourceNotFoundException thrown for missing order
- [x] DLT listener present in OrderEventConsumer
- [x] Idempotency guard present in consumer
- [x] Flyway version V2 is next in sequence (V1 exists, no gap)
- [x] ddl-auto: validate confirmed unchanged
- [x] No hardcoded secrets in application.yml changes
### 📋 Summary
The implementation is structurally sound with 3 blockers that are quick fixes.
The consumer exception handling is the most important - swallowing exceptions
prevents the retry/DLT mechanism from functioning. The missing @Valid and
CONCURRENTLY on the index are both one-word fixes.
Recommendation: Request changes (3 blockers, all < 5 minutes to fix)

Three blockers caught before a human opened the PR. All three are structural — exactly the kind of thing that slows down reviews because reviewers have to read carefully to notice them. Now reviewers can skip straight to “is the business logic correct?”

Agent 3: kafka-topology-agent

A subtler problem: as a Kafka-heavy service grows, it becomes hard to see the full picture. Which topics exist? Who produces to them? Who consumes them? Is every topic that’s produced to actually being consumed? Is every consumer group correctly named?

The kafka-topology-agent reads your entire codebase and generates a live topology map — producers, topics, consumers, consumer groups — and flags gaps.

The AGENT.md

---
name: kafka-topology-agent
description: >
  Reads all producer and consumer classes in the Order Service codebase and
  generates a complete Kafka topology report: topics, producers, consumers,
  consumer groups, and gaps (topics produced to but not consumed, or consumed
  from but not declared). Run whenever the messaging architecture changes or
  when onboarding a new developer.
---

# Kafka Topology Agent
## Your job
Build a complete, accurate picture of the Kafka topology for this service
by reading the actual source code - not documentation, not diagrams.

## Step 1 - Find all Kafka-related classes
Run: find src/main/java -name "*.java" | xargs grep -l "KafkaTemplate\|@KafkaListener" 2>/dev/null

## Step 2 - Read KafkaTopicConfig
Read config/KafkaTopicConfig.java.
Extract all topic name constants and NewTopic beans.
Build the declared topic list.

## Step 3 - Read all producer classes
For each file containing KafkaTemplate:
  - Extract: kafkaTemplate.send(TOPIC, key, payload) calls
  - Map: method name → topic constant → resolved topic string
  - Note: partition key used (should always be aggregateId)

## Step 4 - Read all consumer classes
For each file containing @KafkaListener:
  - Extract: topics = {...}, groupId = "...", containerFactory = "..."
  - Check: is MANUAL ack mode used?
  - Check: is a DLT listener present in the same class?
  - Check: does the consumer group ID follow {service}-{purpose} naming?

## Step 5 - Cross-reference and find gaps
- Topics declared in KafkaTopicConfig but not produced to → orphan topic
- Topics produced to but not in KafkaTopicConfig → undeclared topic
- Topics produced to but no consumer in this service → intentional fan-out (note it)
- Topics consumed but not produced by this service → external dependency (note it)
- DLT topics consumed but matching main topic has no DLT consumer → gap

## Step 6 - Produce topology report
Output format:
---
## Kafka Topology Report - Order Service
Date: {date}
### Declared Topics
| Topic | Partitions | Replicas | Purpose |
|-------|-----------|---------|---------|
| order.events | 12 | 1 | All order domain events |
| order.events.DLT | auto | auto | Dead-letter for order.events |
### Producers
| Class | Method | Topic | Partition Key | Event Type |
|-------|--------|-------|--------------|------------|
| OrderEventProducer | publishOrderPlaced() | order.events | orderId | OrderPlacedEvent |
| OrderEventProducer | publishOrderCancelled() | order.events | orderId | OrderCancelledEvent |
### Consumers
| Class | Topic | Group ID | Manual Ack | DLT Listener | Issues |
|-------|-------|---------|------------|-------------|--------|
| OrderEventConsumer | order.events | order-service-projection | ✓ | ✓ | None |
| OrderEventConsumer | order.events.DLT | order-service-dlt | ✓ | N/A | None |
### Gaps & Warnings
| Type | Detail | Recommendation |
|------|--------|----------------|
| {gap type} | {description} | {fix} |
### Sealed Event Coverage
Events declared in OrderEvent sealed interface vs events handled in consumers:
| Event Type | Produced | Consumed | In Switch |
|------------|---------|---------|-----------|
| OrderPlacedEvent | ✓ | ✓ | ✓ |
| OrderCancelledEvent | ✓ | ✓ | ✓ |
| OrderShippedEvent | ✓ | ✗ | ✗ | ← GAP: produced but no consumer handler
### Summary
{2-3 sentences on overall topology health}
---

That last table — sealed event coverage — is the one that catches the real bugs. An event type added to the sealed interface and a producer method but not yet added to the consumer switch. In a large codebase, this is genuinely hard to spot in review. The agent finds it in seconds.

Agent 4: test-gap-agent

The problem: test coverage reports tell you line coverage. They don’t tell you which specific scenarios aren’t tested. An 80% coverage report on OrderServiceImpl hides the fact that the "order not found" path, the "duplicate event" deduplication path, and the "payment failed" cancellation path have no tests at all.

The test-gap-agent reads your service and controller classes, maps every distinct code path, then reads your test classes and reports exactly which paths are missing tests.

The AGENT.md

---
name: test-gap-agent
description: >
  Analyses a Java service or controller class and its corresponding test class
  to identify untested code paths. Reports missing scenarios by name — not by
  line number. Produces a ready-to-use list of test cases to add. Invoke with
  a service or controller class path.
---

# Test Gap Agent
## Your job
Find the specific missing test scenarios for a Java 21 + Spring Boot class.
Produce a precise, actionable list - not a coverage percentage.

## Step 1 - Read the source class
Read the class provided. For each public method, identify every distinct path:
- Happy path (main success flow)
- Each validation failure path (@Valid, manual checks)
- Each exception path (ResourceNotFoundException, DuplicateResourceException)
- Each conditional branch (if/switch cases)
- Each integration point (repository call, Kafka publish, external service)
Build a path map:
Method → [path 1 description, path 2 description, ...]

## Step 2 - Read the test class
Read the corresponding *Test.java or *IT.java.
For each @Test method, identify what path it covers.
Map test method name → path covered.

## Step 3 - Gap analysis
Cross-reference: paths in source class vs paths covered in tests.
Identify paths with NO corresponding test.

## Step 4 - Produce the gap report AND the test stubs
### Report format:
---
## Test Gap Report - {ClassName}
Analysed: {source file}
Test class: {test file}
Date: {date}
### Coverage by method
#### {methodName}()
- [x] Happy path - {description} - covered by {testMethodName}
- [x] {path} - covered by {testMethodName}
- [ ] {missing path} - NOT TESTED
- [ ] {missing path} - NOT TESTED
### Missing test stubs

Then for EACH missing path, generate the exact test method stub:

@Test
void {methodName}_{scenario}_should{expectedOutcome}() {
    // Arrange
    // {comment describing what to set up}
    // Act
    // {comment describing what to call}
    // Assert
    // {comment describing what to verify}
}

Write these stubs directly into the test class at the end, leaving the Arrange/Act/Assert sections as comments for the developer to fill in.

Rules

  • Be specific: “order not found by UUID” not just “not found case”
  • Group by method — don’t produce a flat list
  • Write real method names following the project convention: {method}_{scenario}_should{outcome}
  • Add stubs to the actual test file — don’t just list them
  • For @KafkaListener tests, always check: duplicate event path, DLT routing path
### What it produces
For `OrderServiceImpl`, the agent might report:

Test Gap Report — OrderServiceImpl

Analysed: src/main/java/com/example/orders/service/OrderServiceImpl.java Test class: src/test/java/com/example/orders/service/OrderServiceTest.java

Coverage by method

create()

  • Happy path — order saved, event published — covered by create_validRequest_shouldSaveAndPublishEvent
  • Duplicate customer order conflict — NOT TESTED
  • Event not published when save() throws — NOT TESTED

findById()

  • Happy path — order found and mapped — covered by findById_existingId_shouldReturnResponse
  • Order not found — NOT TESTED ← this one matters

updateStatus()

  • PENDING → CONFIRMED transition — covered by updateStatus_pendingToConfirmed_shouldUpdate
  • Invalid status transition (DELIVERED → PENDING) — NOT TESTED
  • Concurrent update conflict — NOT TESTED

cancel()

  • Happy path — NOT TESTED at all
  • Cancel already-cancelled order — NOT TESTED

Six untested paths, named precisely, grouped by method. No digging through coverage reports. No guessing what percentage means in practice.

Then, without being asked, the agent writes these stubs directly into OrderServiceTest.java:

@Test
void create_duplicateCustomerOrder_shouldThrowDuplicateResourceException() {
    // Arrange
    // Set up orderRepository to throw DataIntegrityViolationException

    // Act + Assert
    // assertThatThrownBy(() -> orderService.create(request))
    //     .isInstanceOf(DuplicateResourceException.class);
}

@Test
void create_repositorySaveFails_shouldNotPublishEvent() {
    // Arrange
    // when(orderRepository.save(any())).thenThrow(new RuntimeException("db error"))

    // Act + Assert
    // assertThatThrownBy(() -> orderService.create(request));
    // verifyNoInteractions(eventProducer);
}

@Test
void findById_nonExistentId_shouldThrowResourceNotFoundException() {
    // Arrange
    // when(orderRepository.findById(any())).thenReturn(Optional.empty())

    // Act + Assert
    // assertThatThrownBy(() -> orderService.findById(UUID.randomUUID()))
    //     .isInstanceOf(ResourceNotFoundException.class)
    //     .hasMessageContaining("not found");
}

The stubs are already in the file with the correct method names, correct Arrange/Act/Assert structure, and specific comments telling the developer exactly what to mock and what to assert. The cognitive load of “what do I test?” and “how do I structure it?” — both already handled. The developer just fills in the blanks.

Invoking Agents: The Full Workflow

Here’s what a typical Friday afternoon looks like with all four agents available:

# You've finished building the order status update feature
cd order-service && claude

# 1. Generate the migration for the new fields you added to OrderEntity
> Run the db-migration-agent on OrderEntity.java
# Agent runs, generates V2__add_orders_status_history.sql, confirms flyway:info

# 2. Check for test gaps before review
> Run the test-gap-agent on OrderServiceImpl.java
# Agent finds 3 untested paths, writes stubs into OrderServiceTest.java
# You fill in the stubs - takes 15 minutes instead of 30

# 3. Verify the Kafka topology is still coherent after your changes
> Run the kafka-topology-agent
# Agent confirms all events produced are consumed, flags OrderShippedEvent
# as produced but not handled in the consumer switch - you add the case

# 4. Full pre-PR review before opening
> Run the pr-review-agent
# Agent finds 2 blockers (missing @Valid, uncaught exception in consumer)
# You fix both in 10 minutes

# 5. Now open the PR - human reviewer focuses on business logic only
git push origin HEAD

Four agents. One developer. Entire Friday afternoon feature — entity, migration, tests, topology check, PR review — done before opening the PR. The human reviewer gets a PR that’s already structurally correct.

The Compound Effect: All Four Layers Together

This is what the complete setup looks like in practice:

L1 — CLAUDE.md
     "This is a Java 21 + Spring Boot Order Service.
      Kafka topics are named {aggregate}.{past-verb}.
      Every @KafkaListener must have manual ack, idempotency guard, DLT listener."
     → Loaded automatically. Claude knows the context before you type a word.
L2 - Skills
     kafka-consumer skill: "every listener needs these exact four things"
     entity-generator skill: "always produce all five artefacts in order"
     → Claude generates correct code the first time, without being reminded.
L3 - Hooks
     pre-bash-guard: blocks rm -rf, raw DDL, bare mvn
     pre-commit-scan: catches hardcoded secrets before they leave the machine
     post-write-format: formats every Java file automatically after write
     post-write-checkstyle: Claude self-corrects on style violations immediately
     → Standards enforced without prompting. Every session. Every developer.
L4 - Agents
     db-migration-agent: reads entity, generates correct versioned migration
     pr-review-agent: catches structural issues before human review
     kafka-topology-agent: maps full producer/consumer graph, finds gaps
     test-gap-agent: finds untested paths, writes stubs into test files
     → Entire workflows delegated. Results delivered. No supervision required.

Each layer makes the others more valuable. Skills work better because CLAUDE.md gives them context. Hooks work better because Skills give them correctly structured code to enforce standards on. Agents work better because Hooks have already ensured the codebase they’re reading is consistent.

It compounds.

Getting Started With Agents

The activation cost is low — an AGENT.md is just a markdown file:

# Create the agents directory
mkdir -p .claude/agents

# Start with the one that pays off fastest
# For Java + Kafka: db-migration-agent
mkdir -p .claude/agents/db-migration-agent
# Write .claude/agents/db-migration-agent/AGENT.md
# Commit
git add .claude/agents/
git commit -m "chore: add Claude Code agents (L4)"
# Invoke from any session
claude
> Run the db-migration-agent on src/main/java/com/example/orders/domain/OrderEntity.java

Start with db-migration-agent — it has the highest return on the first use. Flyway migration mistakes (wrong version number, missing CONCURRENTLY, wrong NOT NULL handling) are common, consequential, and boring to prevent manually. The agent eliminates all three failure modes in one shot.

Add pr-review-agent next — the ROI is team-wide and immediate. Every PR that goes through it arrives at human review in a structurally better state. Over a sprint, reviewers start commenting on logic, not style.

The Completed Architecture

your-project/
├── CLAUDE.md                           ← L1: Team memory
└── .claude/
    ├── settings.json                   ← L3: Hook wiring + permissions
    ├── skills/                         ← L2: Task-level patterns
    │   ├── entity-generator/SKILL.md
    │   ├── kafka-consumer/SKILL.md
    │   ├── kafka-producer/SKILL.md
    │   ├── db-postgres/SKILL.md
    │   ├── api-layer/SKILL.md
    │   ├── testing/SKILL.md
    │   ├── migration/SKILL.md
    │   ├── security-scan/SKILL.md
    │   └── hooks-manager/SKILL.md
    └── agents/                         ← L4: Autonomous workflow workers
        ├── db-migration-agent/AGENT.md
        ├── pr-review-agent/AGENT.md
        ├── kafka-topology-agent/AGENT.md
        └── test-gap-agent/AGENT.md

Fourteen files. All committed to git. The entire four-layer system — memory, patterns, guardrails, autonomous workers — available to every developer who clones the repo.

What This Series Covered

Three articles. Four layers. One Java 21 + Spring Boot + PostgreSQL + Kafka stack.

  • Part 1 — CLAUDE.md and Skills: Claude generates correct code automatically, following your team’s patterns, without being told.
  • Part 2 — Hooks: Standards enforced structurally around every action. The wrong thing becomes impossible.
  • Part 3 — Agents: Entire workflows delegated. Migrations generated, reviews performed, topology mapped, test gaps found.

The shift, from the beginning: stop treating Claude like a chatbot. Build it like infrastructure. Configure it once, commit it, and watch it compound.


메타데이터
post_id
29b85d9054e6
slug
claude-code-agents-give-your-ai-a-junior-engineer-29b85d9054e6
url
https://medium.com/@shashi.kant93369/claude-code-agents-give-your-ai-a-junior-engineer-29b85d9054e6
canonical_url
https://medium.com/@shashi.kant93369/claude-code-agents-give-your-ai-a-junior-engineer-29b85d9054e6
author_url
https://medium.com/@shashi.kant93369
status
ok
fetched_at
2026-08-19 09:19:56