Intermediate

Agentic AI for Developers

Agentic AI across the SDLC: memory, tools, MCP, RAG, agentic coding and multi-agent observability.

Level: Intermediate


Table of Contents


Module 1 — Understanding Agentic AI

1.1 Machine Learning and NLP

Machine Learning (ML) is a branch of artificial intelligence focused on systems that can learn from experience and improve over time, without being explicitly programmed for each scenario.

ML use cases:

  • Fraud detection: An ML model can analyze millions of transactions and flag suspicious ones.
  • Computer vision: Analyzing X-rays to detect anomalies (fractures, etc.).

The natural language challenge:

Natural language  →  Unstructured data  →  Hard for machines

Example:
"Add a big red apple, two bananas, and a carton of milk to cart"
                    ↓
        NLP (Natural Language Processing)
                    ↓
┌──────────┬──────────┬─────────────┬──────────┐
│  Item    │ Quantity │ Description │ Category │
├──────────┼──────────┼─────────────┼──────────┤
│  apple   │    1     │  big, red   │  fruit   │
│  banana  │    2     │      -      │  fruit   │
│  milk    │    1     │   carton    │  dairy   │
└──────────┴──────────┴─────────────┴──────────┘

NLP (Natural Language Processing) bridges human language and structured data. It works in both directions:

  • Natural language → structured data (comprehension)
  • Structured data → natural language (generation)

NLP use cases:

┌──────────────────────────────────────────────┐
│                    NLP                        │
│                                              │
│  • Automatic translation (EN → FR, JP...)    │
│  • Chatbots                                  │
│  • Sentiment analysis                        │
│  • SPAM detection                            │
└──────────────────────────────────────────────┘

1.2 Large Language Models (LLMs)

A language model is a specific type of ML model trained to understand and generate human language. Its operation is based on probabilistic prediction:

Prediction example:

"The weather today is very ___"

┌──────────────┬─────────────┐
│  Candidate   │ Probability │
├──────────────┼─────────────┤
│    sunny     │     42%     │
│    hot       │     27%     │
│    nice      │     13%     │
│    cloudy    │     11%     │
│    cold      │      7%     │
└──────────────┴─────────────┘

With added context: "I live in Antarctica."

┌──────────────┬─────────────┐
│  Candidate   │ Probability │
├──────────────┼─────────────┤
│    cold      │     66%     │
│    freezing  │     21%     │
│    snowy     │      8%     │
│    harsh     │      3%     │
│    windy     │      2%     │
└──────────────┴─────────────┘

Token: Basic unit of LLM processing. A token can be a whole word or part of a word.

What makes an LLM “large”?

CharacteristicClassic Language ModelLarge Language Model
Training data sizeModestBillions/trillions of tokens (books, websites, code…)
ArchitectureVariedTransformer (attention over the entire input)
Compute power1 CPU / 1 GPUGPU clusters, weeks of training
CapabilitiesNarrow tasks (sentiment, spam)Complex reasoning, text and code generation
ScopeTask-specificGeneral purpose (same model for hundreds of tasks)

LLMs and code:

LLMs understand code because code is text. They can:

  • Read, write, debug, and refactor code
  • Explain code in natural language
// Example code understood and generated by an LLM
function computeOrderTotal(lineItems, taxRate) {
  let subtotal = 0;
  lineItems.forEach(item => {
    subtotal += item.price;
  });
  return subtotal + subtotal * taxRate;
}

The 3 domains where AI can help:

mindmap
  root((AI for Developers))
    Engineering Productivity
      Code generation
      Automated testing
      Code review
      Log analysis
    Operational Efficiency
      Intelligent ticket routing
      Automatic incident summaries
      Self-healing infrastructure
    Product Innovation
      New user experiences
      AI integration in products

1.3 Agentic AI

Evolution of AI systems:

flowchart LR
    subgraph LLM_simple["1 — Single LLM"]
        A1[Input] --> B1[LLM] --> C1[Output]
    end

    subgraph Workflow["2 — Workflows"]
        A2[Inputs] --> B2[LLM 1]
        A2 --> C2[LLM 2]
        B2 --> D2[Aggregator]
        C2 --> D2
        D2 --> E2[Output]
    end

    subgraph Agent["3 — Agents"]
        A3[Input] --> B3[LLM / Reasoning Engine]
        B3 --> C3{Action}
        C3 --> D3[Environment]
        D3 --> E3{Feedback}
        E3 -->|Done| F3[Output]
        E3 -->|Continue| B3
    end

The 4 key characteristics of an AI agent:

┌─────────────────────────────────────────────────────────┐
│                   AI Agent                               │
│                                                         │
│  1. AUTONOMOUS EXECUTION  — Completes tasks on its own  │
│  2. PROACTIVITY           — Takes initiative             │
│  3. GOAL-ORIENTED         — Works toward an objective   │
│  4. COLLABORATION         — Interacts with humans,       │
│                             other agents and systems    │
└─────────────────────────────────────────────────────────┘

Agent lifecycle:

flowchart LR
    P["Perceive\n(Sense the environment)"] --> R["Reason\n(Process with the LLM)"] --> A["Act\n(Execute)"] --> L["Learn\n(Learn from results)"]
    L --> P

Simplified summary: Plan → Act → Adapt

The 3 main components of an AI agent:

graph TD
    Agent["AI Agent"]
    Agent --> RE["Reasoning Engine\n(LLM - the brain)"]
    Agent --> Mem["Memory\n(Short and long term)"]
    Agent --> Tools["Tools\n(Available actions)"]

1.4 Memory and Tools

Detailed agent architecture with Memory and Tools:

flowchart TD
    Input["Input\n(prompt, API call, schedule)"]
    RE["Reasoning Engine (LLM)"]
    Mem["Memory"]
    Tools["Tools"]
    Output["Output"]

    Input --> RE
    RE <--> Mem
    RE --> Tools
    Tools --> RE
    RE --> Output

Memory types:

TypeDescriptionExamples
FilesSimple, readable, easy to manageJSON files
Relational databasesStructured, queryablePostgreSQL
NoSQL / CacheFast access, temporary stateRedis
Dedicated AI solutionsContext management and long-term storageZep

Tool types:

┌───────────────────────────────────────────────────────┐
│                       Tools                           │
│                                                       │
│  Code executor    │  Create file    │  Delete file    │
│  Database query   │  API call       │  PDF parser     │
│  Search web       │  Send email     │  ...            │
└───────────────────────────────────────────────────────┘

1.5 MCP — Model Context Protocol

Problem with tools without MCP:

Without MCP, each agent has its own non-reusable tools:

Agent A : [create_file] [update_file] [delete_file]
Agent B : [create_file] [update_file] [delete_file]  ← Duplication!
Agent C : [create_dir]  [list_dir]   [remove_dir]

Definition:

MCP is an open protocol that standardizes how applications provide context and tools to LLMs.
Source: modelcontextprotocol.io

MCP architecture:

graph LR
    subgraph Host["MCP Host\n(Claude Desktop, IDEs, Frameworks)"]
        Client["MCP Client"]
        LLM["LLM"]
        LLM <--> Client
    end

    subgraph Servers["MCP Servers"]
        FS["MCP Server\nFilesystem\n[create/update/delete file]\n[create/list/delete dir]"]
        PG["MCP Server\nPostgreSQL\n[query/insert/update/delete]"]
        Git["MCP Server\nGit\n[clone/commit/push/branch]"]
    end

    Client <--> FS
    Client <--> PG
    Client <--> Git

    FS --> FSsrc["Filesystem"]
    PG --> PGsrc["PostgreSQL"]
    Git --> Gitsrc["Git Repo"]

MCP components:

ComponentRole
MCP ServerInterface between the LLM and a data source. Exposes tools grouped by type.
MCP ClientIntermediary that maintains the connection to the MCP server.
MCP HostThe environment hosting the client (Claude Desktop, IDEs, agentic frameworks).

1.6 RAG — Retrieval Augmented Generation

The cutoff problem:

Every LLM is trained on data up to a certain point in time (the cutoff). It knows nothing about events occurring after that date.

Timeline:
──────────────────────────────────────────────►
                  │
              Cutoff time
              (e.g.: Jan 2025)
                  │
    [LLM Knowledge]  │  [Unknown to LLM ✗]

Definition:

RAG is a technique that allows LLMs to retrieve and incorporate new information at runtime.
Source: Wikipedia

How RAG works:

flowchart TD
    Q["User question"]
    LLM["LLM"]
    Check{{"Does the LLM\nknow the answer?"}}
    Retrieve["Search\nexternal sources\n(web, files, DB...)"]
    Answer["Enriched answer"]

    Q --> LLM
    LLM --> Check
    Check -->|"No"| Retrieve
    Retrieve --> LLM
    Check -->|"Yes"| Answer
    LLM --> Answer

RAG data sources:

  • Local files (PDF, Word, TXT)
  • Private databases
  • Real-time web search
  • Vector databases

Vector Databases:

Vector databases store numerical representations (embeddings) of data for semantic similarity search:

Object:   Shape  Color  Shininess  Material  Weight
Ball:     [1.0,  1.0,   1.0,       1.0,      1.0 ]
          [1.0,  0.7,   0.9,       0.4,      0.9 ]
          [1.0,  0.5,   0.7,       0.1,      0.8 ]

Query: "Show me something round, yellow and shiny"
       → Vector search → Most similar result

RAG challenges:

ChallengeDescription
Irrelevant retrievalFetching documents unrelated to the question
Long/complex documentsDifficulty extracting the essentials
Security and access controlAvoiding exposure of sensitive data

1.7 Data Security and Compliance

Main Risks

1. Uncontrolled autonomous access

AI agents can become attack targets. Real-world example:

Shortly after the GitHub MCP server was developed, researchers at Invariant Labs discovered a serious vulnerability. The exploit allowed an agent to access private repositories, inject malicious code, and leak sensitive data — without any direct user command.

GitHub MCP exploit:
┌─────────────────────────────────┐
│  1. Access private repos        │
│  2. Inject malicious code       │
│  3. Leak sensitive data         │
└─────────────────────────────────┘

2. Non-compliant behavior

Agents have been reported as:

  • Deleting important project files
  • Attempting to wipe entire directories or critical OS components

EU AI Act

graph TD
    EU["EU AI Act\n(effective August 2026)"]
    EU --> R1["Unacceptable risks\n→ PROHIBITED"]
    EU --> R2["High risks\n→ Strict regulation"]
    EU --> R3["Limited risks\n→ Transparency regulation"]
    EU --> R4["Minimal risks\n→ No regulation"]

Module 2 — Increasing Developer Productivity

2.1 Agentic AI in the SDLC

The software development lifecycle (SDLC) can be improved by AI at each phase:

flowchart LR
    subgraph Plan
        P1["Estimates\n(historical velocity)"]
        P2["Requirements\nanalysis\n(PRD → user stories)"]
        P3["Communication\nplans"]
    end

    subgraph Design
        D1["Rapid\nprototyping"]
        D2["Architecture\ndiagrams"]
        D3["API contracts"]
    end

    subgraph Implement
        I1["Code\ngeneration"]
        I2["Assisted\ncode review"]
        I3["Reverse\nengineering"]
    end

    subgraph Test
        T1["Test\ngeneration"]
        T2["Gap analysis\n(coverage)"]
        T3["Legacy\ncode"]
    end

    subgraph Deploy
        Dep1["Release report"]
        Dep2["Anomaly\ndetection"]
        Dep3["Automatic\ndocumentation"]
    end

    subgraph Maintain
        M1["Internal\nchatbots"]
        M2["Security\nscanning"]
        M3["Postmortems"]
    end

    Plan --> Design --> Implement --> Test --> Deploy --> Maintain

2.2 Agentic Coding

The 3 approaches to AI-assisted code generation:

graph LR
    A["1 — Direct LLMs\n(ChatGPT, Claude)\nManual copy-paste"] -->|Evolution| B["2 — AI Coding Assistants\n(IDE-integrated)\nAutocomplete + Chat"] -->|Evolution| C["3 — Agentic Coding\n(autonomous agent)\nFull task execution"]

Agentic coding tool modes:

ModeTools
IDE PluginGitHub Copilot, Tabnine, Amazon Q, JetBrains AI
Full IDECursor, Windsurf
CLIClaude Code, Aider, OpenAI Codex CLI, Gemini CLI
Web InterfaceGitHub Copilot Workspace, OpenAI Codex, Jules by Google

How an AI coding assistant works:

┌─────────────────────────────────────┐
│         AI Coding Assistant         │
│                                     │
│  Autocomplete                       │
│  ├─ Monitors the active file        │
│  ├─ Predicts next line/block        │
│  └─ Real-time suggestion            │
│                                     │
│  Chat                               │
│  ├─ Access to the entire project    │
│  ├─ Complex questions               │
│  └─ Full block generation           │
└─────────────────────────────────────┘

Autocomplete: optimized for speed and small context (current file).
Chat: access to the entire project, better for complex tasks.

Key concepts:

ConceptDefinition
Vibe CodingApproach where you describe what you want in natural language and let AI generate the code. Ideal for rapid prototyping, but risky in production.
Context EngineeringProviding the right information to the LLM in the right format for it to complete the task efficiently.

Context Engineering — components:

mindmap
  root((Context Engineering))
    Prompt design
    State / History
    Long-term memory
    RAG
    Structured outputs

Agentic coding use cases:

  • Reverse Engineering
  • Code generation
  • Test generation
  • Documentation
  • Refactoring

2.3 Global Rules

Global Rules define the rules that an agent must follow. They are critical for security, compliance, and code consistency.

Why global rules?

┌─────────────────────────────────────────────┐
│               Global Rules                  │
│                                             │
│  Security and compliance                    │
│     → Which tools the agent can use         │
│     → Which actions are forbidden           │
│                                             │
│  Code standards                             │
│     → Style consistent with the codebase   │
│     → Naming conventions                   │
│                                             │
│  Guardrails                                 │
│     → Input protection                     │
│     → Output quality                       │
└─────────────────────────────────────────────┘

Configuration levels:

graph TD
    E["Enterprise policy\n(entire organization)"]
    G["Global personal policy\n(all your projects)"]
    PT["Project policy (team)\n(entire project)"]
    PI["Project policy (individual)\n(you + this project)"]

    E --> G --> PT --> PI

2.4 Demo: Global Rules

Tool used: Claude Code (CLI). Workflow for Global Rules:

# 1. Open a terminal and navigate to the project
cd orders-service

# 2. Start Claude Code
claude

# 3. Run the initialization command
/init

This command automatically generates a CLAUDE.md file — the project “rulebook.”

Prompt used to generate Global Rules:

Please generate a CLAUDE.md file that defines universal development standards 
applicable to all projects.

The document should include:
- Code Style Guidelines: naming conventions, indentation, commenting, file organization
- Version Control Practices: branching strategies, commit message formats, merge protocols
- Testing Requirements: unit testing, integration testing, code coverage expectations
- Documentation Standards: code comments, README files, API documentation
- Security Best Practices: input validation, authentication, dependency management
- CI/CD: automated build, test, deployment processes
- Code Review Procedures: peer reviews, approval processes, code quality checks
- Licensing and Compliance: licensing requirements and compliance

Example generated DEVELOPMENT_STANDARDS.md:

# Naming Conventions
- Variables/Functions: camelCase (getUserName, calculateTotal)
- Classes: PascalCase (UserService, OrderController)
- Constants: UPPER_SNAKE_CASE (MAX_RETRY_COUNT, DEFAULT_TIMEOUT)
- Packages: lowercase (com.example.service)

# Branching Strategy
- main/master: production-ready code
- develop: integration branch
- feature/: new features (feature/user-authentication)
- hotfix/: critical fixes (hotfix/security-patch)
- release/: release preparation (release/v1.2.0)

# Commit Message Format
<type>(<scope>): <description>
Types: feat, fix, docs, style, refactor, test, chore
Example: feat(auth): add OAuth2 integration

# Testing
- Minimum coverage: 80% for new code
- Pattern: Arrange-Act-Assert
- Naming: shouldThrowExceptionWhenUserNotFound

2.5 Demo: One-shot Prompt

Step 1: Create the PRD (Product Requirements Document)

Prompt 1 — PRD generation:

Please create a Product Requirements Document (PRD) in Markdown format for a 
lightweight web service that enables incident reporting and management. 
The service should be written in Java with Spring Boot and support the following:

Key Functional Requirements:
- Users or systems can report incidents (app crashes, failed jobs, customer complaints)
- Incidents are stored in a relational database
- Endpoints: listing, viewing by ID, marking as resolved, assigning a category

Non-Functional Requirements:
- Lightweight and easy to run locally
- RESTful API conventions
- Standard Spring Boot practices

Technical Constraints:
- Language: Java | Framework: Spring Boot | Database: H2 (in-memory)

Generated PRD — API specification:

Base URL: http://localhost:8080/api/v1

POST   /incidents              → Create an incident
GET    /incidents              → List (with pagination + filters)
GET    /incidents/{id}         → View incident by ID
PATCH  /incidents/{id}/status  → Update status
PATCH  /incidents/{id}/category → Update category

Data model:

erDiagram
    INCIDENT {
        Long id PK
        String title
        String description
        IncidentCategory category
        IncidentSeverity severity
        IncidentStatus status
        LocalDateTime createdAt
        LocalDateTime updatedAt
    }

Model enums:

public enum IncidentStatus {
    OPEN, IN_PROGRESS, RESOLVED, CLOSED
}

public enum IncidentSeverity {
    LOW, MEDIUM, HIGH, CRITICAL
}

public enum IncidentCategory {
    DATABASE, APPLICATION, INFRASTRUCTURE, SECURITY, OTHER
}

Step 2: Implement the service

Prompt 2 — Code generation:

Using the content of the file PRD.md as the authoritative product requirements, 
implement a Java + Spring Boot project for an Incident Reporting Web Service.

The service should:
- Allow reporting, listing, resolving, and categorizing incidents
- Use an in-memory H2 database for persistence
- Be lightweight and demo-ready

Please generate:
- Java source code: models, controllers, services, repositories (Spring Data JPA)
- A minimal application.properties for Spring Boot configuration
- A README.md explaining how to run the project

Constraints:
- Skip test implementation for now
- Follow idiomatic Spring Boot conventions
- Maven layout: src/main/java, src/main/resources

Generated architecture — incident-manager-service:

src/main/java/com/example/incidentmanager/
├── IncidentManagerApplication.java
├── controller/
│   ├── IncidentController.java
│   └── GlobalExceptionHandler.java
├── service/
│   └── IncidentService.java
├── repository/
│   └── IncidentRepository.java
├── model/
│   ├── Incident.java
│   ├── IncidentStatus.java
│   ├── IncidentSeverity.java
│   └── IncidentCategory.java
└── dto/
    ├── CreateIncidentRequest.java
    ├── UpdateStatusRequest.java
    ├── UpdateCategoryRequest.java
    └── ErrorResponse.java

Incident.java entity (with Lombok after refactoring):

@Entity
@Table(name = "incidents")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Incident {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank(message = "Title is required")
    @Size(max = 255)
    @Column(nullable = false)
    private String title;

    @Size(max = 2000)
    @Column(length = 2000)
    private String description;

    @NotNull
    @Enumerated(EnumType.STRING)
    private IncidentCategory category;

    @NotNull
    @Enumerated(EnumType.STRING)
    private IncidentSeverity severity;

    @Enumerated(EnumType.STRING)
    private IncidentStatus status = IncidentStatus.OPEN;

    @Column(name = "created_at", nullable = false)
    private LocalDateTime createdAt;

    @Column(name = "updated_at", nullable = false)
    private LocalDateTime updatedAt;

    @PrePersist
    protected void onCreate() {
        LocalDateTime now = LocalDateTime.now();
        createdAt = now;
        updatedAt = now;
    }

    @PreUpdate
    protected void onUpdate() {
        updatedAt = LocalDateTime.now();
    }
}

IncidentService.java:

@Service
@Transactional
public class IncidentService {

    private final IncidentRepository incidentRepository;

    @Autowired
    public IncidentService(IncidentRepository incidentRepository) {
        this.incidentRepository = incidentRepository;
    }

    public Incident createIncident(Incident incident) {
        return incidentRepository.save(incident);
    }

    @Transactional(readOnly = true)
    public Page<Incident> getAllIncidents(
            IncidentStatus status, IncidentCategory category, Pageable pageable) {
        return incidentRepository.findByOptionalStatusAndCategory(status, category, pageable);
    }

    @Transactional(readOnly = true)
    public Incident getIncidentById(Long id) {
        return incidentRepository.findById(id)
            .orElseThrow(() -> new IncidentNotFoundException(id));
    }

    public Incident updateIncidentStatus(Long id, IncidentStatus status) {
        Incident incident = getIncidentById(id);
        incident.setStatus(status);
        return incidentRepository.save(incident);
    }

    public Incident updateIncidentCategory(Long id, IncidentCategory category) {
        Incident incident = getIncidentById(id);
        incident.setCategory(category);
        return incidentRepository.save(incident);
    }
}

IncidentRepository.java:

@Repository
public interface IncidentRepository extends JpaRepository<Incident, Long> {

    Page<Incident> findByStatus(IncidentStatus status, Pageable pageable);
    Page<Incident> findByCategory(IncidentCategory category, Pageable pageable);
    Page<Incident> findByStatusAndCategory(
        IncidentStatus status, IncidentCategory category, Pageable pageable);

    @Query("SELECT i FROM Incident i WHERE " +
           "(:status IS NULL OR i.status = :status) AND " +
           "(:category IS NULL OR i.category = :category)")
    Page<Incident> findByOptionalStatusAndCategory(
        @Param("status") IncidentStatus status,
        @Param("category") IncidentCategory category,
        Pageable pageable
    );
}

application.properties:

spring.application.name=incident-manager-service
server.port=8080

# H2 Database
spring.datasource.url=jdbc:h2:mem:incidentdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password

# JPA/Hibernate
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true

# H2 Console (development only)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# OpenAPI/Swagger
springdoc.api-docs.path=/api-docs
springdoc.swagger-ui.path=/swagger-ui.html

# Actuator
management.endpoints.web.exposure.include=health,info,metrics

2.6 Demo: Reverse Engineering

Reverse Engineering is a safe operation since it modifies no files. It is the best entry point for exploring a new codebase.

The 3 analysis levels:

graph LR
    L1["1 — Overview\n(High level)"] --> L2["2 — Architecture\n(Components)"] --> L3["3 — Deep dive\n(Specific feature)"]

Prompt 1 — Overview:

Summarize what this project does. 
Include main features, tech stack, and key components.

Prompt 2 — Architecture:

Outline the project's architecture. 
List main controllers, services, and how data flows through them.

Prompt 3 — Specific feature:

Find and explain the /api/incidents/{id}/resolve endpoint. 
Show which controller handles it, what it does, 
and how it interacts with services and DB.

Result — Data flow of the GET /incidents/{id} endpoint:

sequenceDiagram
    participant Client
    participant IncidentController
    participant IncidentService
    participant IncidentRepository
    participant H2DB as "H2 DB"

    Client->>IncidentController: GET /api/v1/incidents/{id}
    IncidentController->>IncidentService: getIncidentById(id)
    IncidentService->>IncidentRepository: findById(id)
    IncidentRepository->>H2DB: SELECT * FROM incidents WHERE id=?
    H2DB-->>IncidentRepository: Incident row
    IncidentRepository-->>IncidentService: Optional~Incident~
    alt Incident found
        IncidentService-->>IncidentController: Incident
        IncidentController-->>Client: 200 OK + Incident JSON
    else Incident not found
        IncidentService-->>IncidentController: throw IncidentNotFoundException
        IncidentController-->>Client: 404 Not Found
    end

2.7 Demo: Test Creation

Prompt:

Create unit tests for the getIncidentById method from the IncidentService class.

Claude Code process:

  1. Checks test dependencies (JUnit, Mockito)
  2. Detects missing dependencies and requests permission to add them
  3. Generates the test file
  4. Runs the tests to verify they pass
  5. If a test fails, analyzes and auto-corrects

Generated IncidentServiceTest.java:

@ExtendWith(MockitoExtension.class)
@DisplayName("IncidentService - getIncidentById Tests")
class IncidentServiceTest {

    @Mock
    private IncidentRepository incidentRepository;

    @InjectMocks
    private IncidentService incidentService;

    private Incident sampleIncident;

    @BeforeEach
    void setUp() {
        sampleIncident = new Incident();
        sampleIncident.setId(1L);
        sampleIncident.setTitle("Database Connection Timeout");
        sampleIncident.setDescription("Unable to connect to user database after 30 seconds");
        sampleIncident.setCategory(IncidentCategory.DATABASE);
        sampleIncident.setSeverity(IncidentSeverity.HIGH);
        sampleIncident.setStatus(IncidentStatus.OPEN);
        sampleIncident.setCreatedAt(LocalDateTime.now());
        sampleIncident.setUpdatedAt(LocalDateTime.now());
    }

    @Test
    @DisplayName("Should return incident when valid ID is provided")
    void getIncidentById_WithValidId_ReturnsIncident() {
        // Arrange
        when(incidentRepository.findById(1L)).thenReturn(Optional.of(sampleIncident));

        // Act
        Incident result = incidentService.getIncidentById(1L);

        // Assert
        assertThat(result).isNotNull();
        assertThat(result.getId()).isEqualTo(1L);
        assertThat(result.getTitle()).isEqualTo("Database Connection Timeout");
        verify(incidentRepository, times(1)).findById(1L);
    }

    @Test
    @DisplayName("Should throw IncidentNotFoundException when incident does not exist")
    void getIncidentById_WithInvalidId_ThrowsIncidentNotFoundException() {
        // Arrange
        when(incidentRepository.findById(999L)).thenReturn(Optional.empty());

        // Act & Assert
        assertThatThrownBy(() -> incidentService.getIncidentById(999L))
            .isInstanceOf(IncidentNotFoundException.class)
            .hasMessage("Incident with ID 999 not found");

        verify(incidentRepository, times(1)).findById(999L);
    }
}

Best practice: Incremental approach — test method by method rather than the entire project at once.


2.8 Demo: Documentation

Prompt 1 — Review and improvement:

Review README.md and suggest improvements. 
Check for clarity, completeness, outdated steps, 
and missing setup or usage info.

Prompt 2 — External documentation:

Rewrite the API documentation to be suitable for external partners. 
Make it concise, formal, and remove internal implementation notes.

Example generated external documentation (EXTERNAL_DOCUMENTATION.md):

# Incident Management API — v1

**Base URL**: https://api.example.com/api/v1
**Content-Type**: application/json

## POST /incidents
Creates a new incident.

Request Body:
{
  "title": "string (required, max 255 chars)",
  "description": "string (optional, max 2000 chars)",
  "category": "DATABASE|APPLICATION|INFRASTRUCTURE|SECURITY|OTHER",
  "severity": "LOW|MEDIUM|HIGH|CRITICAL"
}

Response 201:
{
  "id": 123,
  "title": "Database Connection Timeout",
  "category": "DATABASE",
  "severity": "HIGH",
  "status": "OPEN",
  "createdAt": "2024-01-15T10:30:00Z"
}

Use case: Documentation is particularly useful after adding new features or onboarding external partners.


2.9 Demo: Refactoring

Golden rule: Always have a complete test suite before refactoring. Tests are your safety net.

Recommended approach:

  • Small, incremental changes
  • One component at a time
  • Validate tests after each change

Prompt:

Refactor all Java classes to remove boilerplate getters, setters, constructors, 
and toString. Use Lombok annotations like @Getter, @Setter, @AllArgsConstructor, 
@NoArgsConstructor, and @ToString.

Before refactoring (simplified Incident.java):

public class Incident {
    private Long id;
    private String title;
    // ...

    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    // ... dozens of additional lines
}

After refactoring (with Lombok):

@Entity
@Table(name = "incidents")
@Data           // Replaces getters, setters, toString, equals, hashCode
@NoArgsConstructor
@AllArgsConstructor
public class Incident {
    private Long id;
    private String title;
    // ... much cleaner!
}

Performance note: Refactoring a small codebase took Claude Code ~11 minutes. For larger projects, plan accordingly.


2.10 CI/CD Pipelines

AI can be integrated as a standard step in CI/CD pipelines via an SDK:

flowchart LR
    subgraph CI Pipeline
        Build["Build"] --> Test["Tests"]
        Test -->|Tests fail| AIAgent["AI Agent Step\n(via SDK)"]
        AIAgent -->|Analyzes & fixes| Test
        Test -->|Tests pass| Deploy["Deploy"]
    end

AI use cases in CI/CD:

PhaseUse Case
BuildAnomaly detection in logs
TestAuto-correction of failing tests
TestGenerating new tests if coverage is low
ImplementationGenerating missing code (PR context)
Code ReviewAutomatic review + PR comments
ReleaseRelease report generation
DocumentationAutomatic documentation of changes

SDK architecture in the pipeline:

Pipeline Step
     │
     ▼
┌─────────────────────────────────────┐
│           LLM SDK                   │
│  ├─ LLM connection                  │
│  ├─ Access to CI/CD tools           │
│  ├─ Build context                   │
│  └─ Actions: read, write, test      │
└─────────────────────────────────────┘
     │
     ▼
  LLM (Claude, GPT, etc.)

Module 3 — Improving Operational Efficiency and Innovating Products

3.1 Agentic Frameworks

Main AI patterns for operational efficiency:

graph LR
    subgraph Patterns
        W["1 — AI Workflows\n(Step orchestration)"]
        A["2 — AI Agents\n(Autonomous, reasoning)"]
        M["3 — Multi-agent Systems\n(Agent collaboration)"]
    end
    W --> A --> M

Types of agentic frameworks:

┌──────────────────────────────────────────────────────────┐
│                Agentic Frameworks                        │
│                                                         │
│  No-code platforms        Coding frameworks              │
│  ────────────────         ─────────────────              │
│  • n8n                    • LangGraph                    │
│  • Relevance AI           • PydanticAI                   │
│  • Flowise                • Microsoft AutoGen            │
└──────────────────────────────────────────────────────────┘

Product innovation:

graph LR
    ML["Machine Learning\n(Predictive models)"] --> Prod["Product\nInnovation"]
    GenAI["Generative AI\n(LLMs, generation)"] --> Prod

3.2 Demo: Incident Enrichment

Tool: n8n (no-code, visual interface)
Goal: Automatically enrich incident data with an AI agent.

Workflow architecture:

flowchart LR
    Trigger["Chat Trigger\n(or Webhook)"] --> Agent["AI Agent\n(Incident Triage)"]
    Agent --> Output["Enriched Report"]

Enrichment agent prompt:

You are an AI agent that performs incident triage. 
When given incident details, immediately:

1. Summarize the incident briefly.
2. Classify it by:
   - Severity (Critical, High, Medium, Low)
   - Source/system involved (e.g., database, network, auth)
3. Suggest likely causes if technical context is available.

Do not ask for more input or wait for confirmation. 
Always respond directly with analysis.

Input: {{ $json.chatInput }}

Input JSON payload:

{
  "id": 123,
  "title": "Database Connection Timeout",
  "description": "Unable to connect to user database after 30 seconds",
  "category": "DATABASE",
  "status": "OPEN",
  "createdAt": "2024-01-15T10:30:00Z",
  "updatedAt": "2024-01-15T10:30:00Z"
}

n8n agent configuration:

ParameterDescription
Chat modelThe reasoning engine (required)
ToolsAvailable actions for the agent
MemoryContext persistence between executions

3.3 Demo: Memory

Goal: Allow the agent to relate linked incidents to each other.

Memory configuration in n8n:

  • Type: Simple memory (window buffer)
  • Window: 10 last interactions (default: 5)

Prompt with memory:

You are an AI agent that performs incident triage. When given incident details:

1. Summarize the incident briefly.
2. Review recent incidents from memory and check for any that:
   - Affect the same or related systems (database, auth, frontend)
   - Appear to be recurring or escalating versions of a past issue
   - May share a root cause (timeouts, DB latency, systemic slowdowns)
3. If related incidents are found, link them together and include:
   - Reference to the past incident(s) (e.g., "similar to Incident #123")
   - Assessment of escalation, recurrence, or widening scope
   - Updated severity if recurrence indicates increased impact
4. Classify by: Severity + Source/system involved
5. Suggest likely causes.

Input: {{ $json.chatInput }}

Memory in action demonstration:

Run 1: Incident #123 - Database Connection Timeout
→ Result: "No related incidents found in recent memory"

Run 2: Incident #124 - User Service Fails to Retrieve Profiles
       (mentions database latency issues)
→ Result: "⚠️ Linked to Incident #123 — same DB problem"

Run 3: Incident #125 - Multiple Microservices Failing Due to DB Latency
       (auth, billing, user — timeout >35s)
→ Result: "🚨 Escalation detected — Incidents #123 and #124 linked"

Test payloads:

// Incident #124
{
  "id": 124,
  "title": "User Service Fails to Retrieve Profiles",
  "description": "User service is experiencing latency retrieving profile data from the database. Requests take over 25 seconds.",
  "category": "DATABASE",
  "status": "OPEN",
  "createdAt": "2024-01-16T08:42:00Z"
}

// Incident #125
{
  "id": 125,
  "title": "Multiple Microservices Failing Due to DB Latency",
  "description": "Several microservices (auth, billing, user) are timing out when querying the database. Latency exceeds 35s.",
  "category": "DATABASE",
  "status": "OPEN",
  "createdAt": "2024-01-17T11:15:00Z"
}

Impact: Memory enables much faster root cause analysis by connecting incidents that appear isolated.


3.4 Demo: Tools

Goal: Use external data (Google Sheets) for severity classification and send automatic email notifications (Gmail).

Workflow architecture with tools:

flowchart LR
    Input["Incident\n(JSON)"] --> Agent["AI Agent"]
    Agent --> GS["Google Sheets\n(Severity Mapping)"]
    Agent --> GM["Gmail\n(Send email)"]
    GS --> Agent
    Agent --> Report["Final report\n+ Email sent"]

Prompt with tools:

[Includes same triage + memory tasks, then:]

4. Classify by:
   - Severity: Refer to the Google Sheet of severity mappings for guidance.
     Consider keywords, patterns, or context that match known mappings to assign:
     Critical, High, Medium, or Low.
   - Source/system involved.

After analysis, immediately send an email:

Subject: New Incident: *Summary* [*Severity*]
Body:
<p>Hello Team,</p>
<p>A new incident has been reported:</p>
<ul>
  <li><strong>Summary:</strong> *Summary*</li>
  <li><strong>Severity:</strong> *Severity*</li>
  <li><strong>Source:</strong> *Source*</li>
  <li><strong>Suggested Cause:</strong> *Suggested Cause*</li>
</ul>
<p>This notification was auto-generated by the AI Incident Enrichment Agent.</p>

3.5 Demo: Guardrails

Goal: Protect the workflow from invalid, dangerous, or malformed inputs.

Guardrail types:

TypeRole
Relevance classifierVerifies that input is within the expected scope
Safety classifierDetects prompt injections and malicious inputs
PII filterFilters personally identifiable data
ModerationFlags harmful or inappropriate content
Tool safeguardDynamically evaluates and restricts tool access

Workflow architecture with guardrail:

flowchart LR
    Input["User\ninput"] --> Guard["Guardrail Agent\n(Validation)"]
    Guard -->|ValidIncident| Enricher["Incident\nEnricher Agent"]
    Guard -->|InvalidIncident| Reject["Rejection\n+ Explanation"]
    Enricher --> Output["Final\nReport"]

Guardrail Agent prompt:

You are an AI guardrail agent protecting the incident enrichment workflow.

Tasks:
1. Determine if input qualifies as a valid incident:
   - Clear description of a system failure, error, or issue
   - Affected system or area (login service, database, etc.)

2. Reject non-incident inputs:
   - Random messages ("hi", "check this out")
   - Feature requests, opinions, support tickets unrelated to system errors
   - Blank or ambiguous messages

3. Sanitize and normalize the input:
   - Trim excessive whitespace
   - Infer category/source if unclear

4. Classify as: ValidIncident OR InvalidIncident

Output format (JSON):
{
  "status": "<ValidIncident | InvalidIncident>",
  "sanitizedInput": "<cleaned version>",
  "reason": "<short explanation>"
}

Input: {{ $json.chatInput }}

3.6 Multi-Agent Systems

Definition: Multiple AI agents collaborate, hand off tasks, and automate complex processes end to end.

The 4 conversational patterns:

Pattern 1: Two-agent Chat

flowchart LR
    A1["Agent A"] <--> A2["Agent B"]

Ideal for simple scenarios requiring minimal coordination.

Pattern 2: Sequential Chat

flowchart LR
    CW["Content\nWriter"] --> SEO["SEO\nOptimizer"] --> IG["Image\nGenerator"] --> PUB["Publisher"]

Example: Content publishing pipeline. Each agent completes its task before passing to the next.

Pattern 3: Group Chat

flowchart TD
    GCM["Group Chat\nManager\n(Orchestrator)"]
    GCM --> TL["Timeline\nAgent"]
    GCM --> BA["Budget\nAgent"]
    GCM --> RM["Resource\nManagement Agent"]
    GCM --> RA["Risk\nAssessment Agent"]
    TL --> GCM
    BA --> GCM
    RM --> GCM
    RA --> GCM

Example: Project management — parallel tasks coordinated by an orchestrator.

Pattern 4: Nested Chat

flowchart TD
    CM["Campaign\nManager"]
    subgraph Production["Production Team"]
        CC["Content\nCreator"]
        HO["Hashtag\nOptimizer"]
    end
    subgraph Strategy["Strategy Team"]
        TA["Trend\nAnalyzer"]
        AI["Audience\nInsights"]
    end
    CM --> Production
    CM --> Strategy
    CC & HO --> CM
    TA & AI --> CM

Example: Social media campaign — hierarchical structure with independent teams.

Pattern comparison:

PatternUse CaseComplexity
Two-agent chatSimple coordinationLow
Sequential chatLinear pipelineMedium
Group chatParallel tasksHigh
Nested chatComplex hierarchiesVery high

3.7 Agent Observability and Evaluation

Difference between Testing and Evaluation:

Testing                          Evaluation
───────────────────────          ─────────────────────────────────
Checks the OUTPUT                Analyzes the entire REASONING PROCESS
• Does the code compile?         • Which internal prompts were used?
• Are requirements met?          • How many tokens were consumed?
• Is it what was requested?      • Was memory used correctly?
                                 • Were the right tools called?

Evaluation methods:

graph TD
    E["Agent Evaluation"]
    E --> H["Human-in-the-loop\n(Pause after each step)"]
    E --> L["LLM-as-a-judge\n(Another LLM evaluates)"]
    E --> T["Task completion\n(Metrics across multiple runs)"]
    E --> O["Observability frameworks\n(Detailed traces)"]

Observability frameworks:

FrameworkDescription
Opik (Comet)Open-source. Traces the reasoning process step by step.
LangfuseDetailed analytics, prompt tracking, decision flow visualization.

Evaluation metrics (Task Completion):

• Success rate
• Execution time  
• Tool usage
• Token consumption
• Number of iterations required

Complete observability cycle:

flowchart LR
    Agent["Agent in production"]
    OFW["Observability Framework\n(Opik / Langfuse)"]
    Metrics["Metrics"]
    Eval["Evaluation"]
    Improve["Prompt/tool\nimprovement"]

    Agent -->|Traces| OFW
    OFW --> Metrics
    Metrics --> Eval
    Eval --> Improve
    Improve --> Agent

General Summary

Course Overview

mindmap
  root((Agentic AI for Developers))
    Module 1 - Understanding
      ML and NLP
      LLMs and tokens
      Agents
        Perceive - Reason - Act - Learn
      Memory
        JSON, PostgreSQL, Redis, Zep
      Tools
      MCP Protocol
      RAG
      Security and EU AI Act
    Module 2 - Dev Productivity
      Complete SDLC
      Agentic Coding
        Claude Code, Cursor, Copilot
      Global Rules
        CLAUDE.md
      Demos
        PRD - Code - Tests - Docs - Refactor
      CI/CD with AI
    Module 3 - Operations and Products
      Frameworks
        n8n, LangGraph, AutoGen
      Incident Enrichment
      Memory in workflows
      External tools
        Google Sheets, Gmail
      Guardrails
      Multi-agent Systems
      Observability
        Opik, Langfuse

Key Terminology

TermDefinition
MLMachine Learning — systems that learn from data
NLPNatural Language Processing — bridge between human language and machines
LLMLarge Language Model — large-scale language model (transformer)
TokenBasic unit of an LLM (word or part of a word)
Agentic AIAI that perceives, reasons, acts and learns autonomously
MCPModel Context Protocol — standardizes tool access for LLMs
RAGRetrieval Augmented Generation — enriching the LLM with external data
Vibe CodingGenerating code by natural language description
Context EngineeringArt of providing the right context to the LLM to maximize quality
GuardrailsSecurity mechanisms to control agent inputs/outputs
CutoffTraining data cutoff date for an LLM
HallucinationLLM generation of incorrect but plausible information

Search Terms

agentic · ai · developers · agents · orchestration · artificial · intelligence · generative · chat · pattern · global · memory · rules · tools

Interested in this course?

Contact us to book it or get a custom training plan for your team.