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:
- Open Bob and set up a connection to your IBM i host using SSH or other secure methods.
- Point it to your source libraries and files.
- 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!
