IBM Project Bob: AI-Powered Documentation for Legacy RPG Code

  • Home
  • /
  • Blog
  • /
  • IBM Project Bob: AI-Powered Documentation for Legacy RPG Code

February 20, 2026

IBM Project Bob: AI-Powered Documentation for Legacy RPG Code

By NickLitten

February 20, 2026

IBM BOB, modernization, RPG, RPGLE

Today we’re diving into something seriously exciting for anyone working with IBM i or AS/400 systems: IBM’s Project Bob, an AI-driven powerhouse with an incredible ability to analyze and document those ancient, mysterious RPG source codes. You know the ones. Dusty fixed-format RPG III programs from the 90s, packed with logic but completely lacking comments. I’ll be firing up Bob, feeding it some RPG, and letting the AI work its magic with instant analysis, smart documentation, clear explanations, and even modernization suggestions. No more guessing what that old subroutine does. Bob makes it crystal clear. Stick around, because this could transform the way you handle legacy code. Let’s get documenting.

What is Project Bob?

Project Bob is IBM’s new AI code assistant, built as a fork of Visual Studio Code. It’s tailored for IBM i developers working with RPG, CL, DDS, and SQL. Think of it as your smart sidekick that understands legacy code and helps modernize it. Unlike general-purpose AI tools, Bob is fine-tuned for IBM i environments. It can analyze entire projects, including physical files, display files, and source members. This means it grasps how everything connects, from database structures to program logic.

Key features include:

  • Code explanation: Breaks down what the code does in plain English.
  • Automatic documentation: Adds headers, inline comments, and field descriptions.
  • Modernization suggestions: Recommends ways to refactor fixed-format RPG to free-format or improve efficiency.
  • Vulnerability detection: Spots security issues early.
  • Code generation: Helps create new RPG code from prompts.

For those maintaining old AS/400 or ISERIES apps, Bob saves hours by turning undocumented messes into readable, maintainable code.

Getting Started with Project Bob

First, you’ll need to download Project Bob from IBM’s site. It’s currently in preview, so check the waitlist or early access programs if it’s not fully rolled out yet. Since it’s based on VS Code, installation is straightforward. Run the installer on your Windows, macOS, or Linux machine.

Once installed, connect Bob to your IBM i system:

  1. Open Bob and set up a connection to your IBM i host using SSH or other secure methods.
  2. Point it to your source libraries and files.
  3. Bob will index your project, analyzing RPG source, DDS definitions, and related artifacts.

No heavy system requirements mentioned, but ensure your machine has decent RAM for handling large codebases. If you’re familiar with VS Code, the interface will feel right at home, with added AI panels for chatting and reviewing code.

Putting Bob to Work: A Real RPG Example

Let’s walk through a practical demo. Suppose you have a RPGLE program that’s a web service for managing a food file. It handles CRUD operations: get, add, update, delete. The code is reasonably written but missing documentation, no comments, and it’s been sitting untouched for years.

Fire up Bob, load the RPG source member, and ask it to “document this program.” In seconds, Bob analyzes the code:

  • It identifies the mainline subprocedure and control options.
  • Adds a functional header: “This program serves as a backend web service for managing food records, processing input parameters like function code and returning status fields.”
  • Inserts inline comments for logic flow, explaining chains, reads, and updates.
  • Documents the file structure, including externally described fields from the DDS.

Bob even suggests modernizations, like converting to free-format RPG or adding error handling best practices.

Watch the full demo in this video where I show Bob in action:

In the video, you’ll see how Bob turns a cryptic program into something any developer can pick up quickly. It’s like having an expert RPG guru review your code on demand.

RPG Code Before:

**free
//
// program name: WEBFOOD-Sample_Webservice_for_FOODFILE.pgm.sqlrpgle
// description: Sample RPGLE program to provide webservice access to FOODFILE
//
// modification history:                                                 
// v.000 2025.10.19 njl created for online example     
//

ctl-opt
main(mainline)
    optimize(*full)
    option(*nodebugio:*srcstmt:*nounref)
    pgminfo(*pcml:*module)
    actgrp(*new)
    indent('| ')
    alwnull(*usrctl)
    copyright('WEBFOOD | V.000 | Sample CGI Webservice');

    dcl-f FOODFILE usage(*INPUT:*OUTPUT:*UPDATE:*DELETE) keyed usropn rename(FOODFILE:RECFOOD);

    dcl-proc mainline;
        dcl-pi *n;
            function char(3);
            rtntext char(100);
            data likeds(FoodRec);
        end-pi;

        // fields come from FOODFILE record format
        dcl-ds FoodRec extname('FOODFILE':*input) qualified inz; // EXTNAME binds the file layout
        end-ds;

        monitor;

            if function <> 'GET' and function <> 'ADD' and function <> 'UPD' and function <> 'DLT';
                rtntext = '(INVALID) Function must be READ/ADD/UPDATE/DELETE: ' + %trim(function);
            else;

                open foodfile;

                select;
                    when function = 'GET';
                        chain (data.INGID) FOODFILE;
                        if %found(FOODFILE);
                            data.INGNAME = INGNAME;
                            data.CATEGORY = CATEGORY;
                            data.MEASURE = MEASURE;
                            data.QUANTITY = QUANTITY;
                            data.EXPDATE = EXPDATE;
                            data.ORGANIC = ORGANIC;
                            rtntext = 'Row ' + %char(data.INGID) + ' Successfully Read';
                        else;
                            rtntext = '(READ FAIL) Ingredient ID does not exist: ' + %char(data.INGID);
                        endif;

                    when function = 'ADD';
                        chain (data.INGID) FOODFILE;
                        if not %found(FOODFILE);
                            INGNAME = %trimr(data.INGNAME);
                            CATEGORY = %trimr(data.CATEGORY);
                            MEASURE = %trimr(data.MEASURE);
                            QUANTITY = data.QUANTITY;
                            EXPDATE = data.EXPDATE;
                            ORGANIC = data.ORGANIC;
                            write RECFOOD;
                            rtntext = 'Row ' + %char(data.INGID) + ' Successfully Added';
                        else;
                            rtntext = '(ADD FAIL) Ingredient ID already exists: ' + %char(data.INGID);
                        endif;

                    when function = 'UPD';
                        chain (data.INGID) FOODFILE;
                        if %found(FOODFILE);
                            INGNAME = %trimr(data.INGNAME);
                            CATEGORY = %trimr(data.CATEGORY);
                            MEASURE = %trimr(data.MEASURE);
                            QUANTITY = data.QUANTITY;
                            EXPDATE = data.EXPDATE;
                            ORGANIC = data.ORGANIC;
                            update RECFOOD;
                            rtntext = 'Data Successfully Updated';
                        else;
                            rtntext = '(UPDATE FAIL) Ingredient ID does not exist for UPDATE: ' + %char(data.INGID);
                        endif;

                    when function = 'DLT';
                        chain (data.INGID) FOODFILE;
                        if %found(FOODFILE);
                            delete RECFOOD;
                            rtntext = 'Row ' + %char(data.INGID) + ' Successfully Deleted';
                        else;
                            rtntext = '(DELETE FAIL) Ingredient ID does not exist for DELETE: ' + %char(data.INGID);
                        endif;

                endsl;

                close foodfile;

            endif;

            return;

        on-error;
            rtntext = '(ERROR) Main Program Error: ' + %char(%status);
        endmon;

end-proc;

RPG Code After:

**free
//==============================================================================
// Program Name: WEBFOOD-Sample_Webservice_for_FOODFILE.pgm.rpgle
// Description:  RESTful webservice program providing CRUD operations for
//               FOODFILE database. Implements GET, ADD, UPD, and DLT functions
//               for managing food inventory ingredients.
//
// Purpose:      This program serves as a backend webservice that can be called
//               via HTTP requests to manage food ingredient records. It handles
//               all basic database operations with proper error handling and
//               validation.
//
// Usage:        Called with three parameters:
//               1. function (char 3)  - Operation: 'GET', 'ADD', 'UPD', or 'DLT'
//               2. rtntext (char 100) - Return message/status text
//               3. data (FoodRec DS)  - Data structure containing ingredient info
//
// Database:     FOODFILE - Contains ingredient records with fields:
//               - INGID    : Ingredient ID (key field)
//               - INGNAME  : Ingredient name
//               - CATEGORY : Food category
//               - MEASURE  : Unit of measurement
//               - QUANTITY : Quantity amount
//               - EXPDATE  : Expiration date
//               - ORGANIC  : Organic flag
//
// Modification History:
// v.000 2025.10.19 njl - Initial creation for online example
//==============================================================================


//------------------------------------------------------------------------------
// Control Options
//------------------------------------------------------------------------------
ctl-opt
    main(mainline)                              // Entry point procedure
    optimize(*full)                             // Full optimization for performance
    option(*nodebugio:*srcstmt:*nounref)       // Debug options
    pgminfo(*pcml:*module)                     // Generate PCML for webservice
    actgrp(*new)                               // New activation group per call
    indent('| ')                               // Source indentation character
    alwnull(*usrctl)                           // Allow null-capable fields
    copyright('WEBFOOD | V.000 | Sample CGI Webservice');

//------------------------------------------------------------------------------
// File Declarations
//------------------------------------------------------------------------------
// FOODFILE: Main ingredient database file
// - Keyed access by INGID (ingredient ID)
// - User-controlled open (usropn) for explicit file management
// - Record format renamed to RECFOOD for clarity
// - Supports all CRUD operations: INPUT, OUTPUT, UPDATE, DELETE
dcl-f FOODFILE usage(*INPUT:*OUTPUT:*UPDATE:*DELETE) keyed usropn rename(FOODFILE:RECFOOD);

//==============================================================================
// Main Procedure: mainline
//
// Purpose: Entry point for webservice. Processes CRUD operations on FOODFILE
//          based on the function parameter received.
//
// Parameters:
//   function (input)  - Operation code: 'GET', 'ADD', 'UPD', or 'DLT'
//   rtntext (output)  - Status message returned to caller
//   data (input/output) - Data structure containing ingredient information
//
// Returns: Status message in rtntext parameter
//==============================================================================
dcl-proc mainline;
    dcl-pi *n;
        function char(3);           // Operation: GET/ADD/UPD/DLT
        rtntext char(100);          // Return status message
        data likeds(FoodRec);       // Ingredient data structure
    end-pi;

    //--------------------------------------------------------------------------
    // Data Structure: FoodRec
    // Externally defined from FOODFILE record format
    // Contains all fields from the ingredient database record
    //--------------------------------------------------------------------------
    dcl-ds FoodRec extname('FOODFILE':*input) qualified inz;
    end-ds;

    //--------------------------------------------------------------------------
    // Main Processing Logic with Error Handling
    //--------------------------------------------------------------------------
    monitor;

        // Validate function parameter - must be one of four valid operations
        if function <> 'GET' and function <> 'ADD' and function <> 'UPD' and function <> 'DLT';
            rtntext = '(INVALID) Function must be GET/ADD/UPD/DLT: ' + %trim(function);
        else;

            // Open the food ingredient file for processing
            open foodfile;

            //------------------------------------------------------------------
            // Process Request Based on Function Type
            //------------------------------------------------------------------
            select;
                //--------------------------------------------------------------
                // GET Operation: Retrieve ingredient record by ID
                // Returns all ingredient details if found
                //--------------------------------------------------------------
                when function = 'GET';
                    // Attempt to read record using ingredient ID as key
                    chain (data.INGID) FOODFILE;
                    
                    if %found(FOODFILE);
                        // Record found - populate return data structure
                        data.INGNAME = INGNAME;
                        data.CATEGORY = CATEGORY;
                        data.MEASURE = MEASURE;
                        data.QUANTITY = QUANTITY;
                        data.EXPDATE = EXPDATE;
                        data.ORGANIC = ORGANIC;
                        rtntext = 'Row ' + %char(data.INGID) + ' Successfully Read';
                    else;
                        // Record not found - return error message
                        rtntext = '(READ FAIL) Ingredient ID does not exist: ' + %char(data.INGID);
                    endif;

                //--------------------------------------------------------------
                // ADD Operation: Create new ingredient record
                // Validates that ID doesn't already exist before adding
                //--------------------------------------------------------------
                when function = 'ADD';
                    // Check if ingredient ID already exists
                    chain (data.INGID) FOODFILE;
                    
                    if not %found(FOODFILE);
                        // ID is unique - safe to add new record
                        // Trim trailing spaces from character fields
                        INGNAME = %trimr(data.INGNAME);
                        CATEGORY = %trimr(data.CATEGORY);
                        MEASURE = %trimr(data.MEASURE);
                        QUANTITY = data.QUANTITY;
                        EXPDATE = data.EXPDATE;
                        ORGANIC = data.ORGANIC;
                        
                        // Write new record to database
                        write RECFOOD;
                        rtntext = 'Row ' + %char(data.INGID) + ' Successfully Added';
                    else;
                        // Duplicate ID - cannot add
                        rtntext = '(ADD FAIL) Ingredient ID already exists: ' + %char(data.INGID);
                    endif;

                //--------------------------------------------------------------
                // UPD Operation: Update existing ingredient record
                // Validates that record exists before updating
                //--------------------------------------------------------------
                when function = 'UPD';
                    // Locate record to update by ingredient ID
                    chain (data.INGID) FOODFILE;
                    
                    if %found(FOODFILE);
                        // Record found - update all fields
                        // Trim trailing spaces from character fields
                        INGNAME = %trimr(data.INGNAME);
                        CATEGORY = %trimr(data.CATEGORY);
                        MEASURE = %trimr(data.MEASURE);
                        QUANTITY = data.QUANTITY;
                        EXPDATE = data.EXPDATE;
                        ORGANIC = data.ORGANIC;
                        
                        // Commit changes to database
                        update RECFOOD;
                        rtntext = 'Data Successfully Updated';
                    else;
                        // Record not found - cannot update
                        rtntext = '(UPDATE FAIL) Ingredient ID does not exist for UPDATE: ' + %char(data.INGID);
                    endif;

                //--------------------------------------------------------------
                // DLT Operation: Delete ingredient record
                // Validates that record exists before deleting
                //--------------------------------------------------------------
                when function = 'DLT';
                    // Locate record to delete by ingredient ID
                    chain (data.INGID) FOODFILE;
                    
                    if %found(FOODFILE);
                        // Record found - proceed with deletion
                        delete RECFOOD;
                        rtntext = 'Row ' + %char(data.INGID) + ' Successfully Deleted';
                    else;
                        // Record not found - cannot delete
                        rtntext = '(DELETE FAIL) Ingredient ID does not exist for DELETE: ' + %char(data.INGID);
                    endif;

            endsl;

            // Close file after processing - ensures proper cleanup
            close foodfile;

        endif;

        // Normal procedure exit
        return;

    //--------------------------------------------------------------------------
    // Error Handler
    // Catches any unexpected errors during processing
    // Returns error status code to caller
    //--------------------------------------------------------------------------
    on-error;
        rtntext = '(ERROR) Main Program Error: ' + %char(%status);
    endmon;

end-proc;

Why This Changes Everything for IBM i Programmers

Legacy code is the backbone of many IBM i shops but maintaining it without docs is a nightmare. Project Bob fixes that by automating the tedious parts. You get clear explanations, which helps onboard new programmers or debug issues faster. Plus, the modernization tips push your code toward modern standards without a full rewrite.

If you’re dealing with RPG III or IV, give Bob a try. It supports various RPG styles and integrates with your existing workflow. Just remember, while it’s great for analysis, always review generated code for your specific business logic.

Questions? Drop a comment below. Happy coding!

{"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}

Join the IBM i Community for FREE Presentations, Lessons, Hints and Tips

>