Objectives and steps
A GenLabZ project is broken down into objectives and steps. Getting this structure right is the most important part of authoring — everything else (instructions, code, evaluation) flows from it.
Objectives
An objective is a purpose-oriented grouping of steps. It describes what the student is working toward in that phase of the project, not what they physically do.
| Good objective titles | Why |
|---|---|
| ”Provision the DynamoDB table for persistent storage” | Describes the purpose |
| ”Implement the business logic in a Lambda function” | Describes the purpose |
| Bad objective titles | Why |
|---|---|
| ”Students will learn to create S3 buckets” | That’s a learning outcome, not an objective |
| ”Navigate to the Lambda console and create a function” | Those are instructions |
The objective’s description field is student-facing — write it in markdown and make it engaging. It should explain what this objective achieves and why it matters in the context of the project. If you need more space or richer formatting, you can override it with a content/{objective_id}.md file.
Steps
A step is a discrete, testable action within an objective. Each step should produce something concrete that can be verified — a resource created, a configuration applied, a connection established.
Aim for 2–4 steps per objective. A step that does too much is hard to evaluate precisely; a step that does too little creates unnecessary overhead.
| Good step titles | Why |
|---|---|
| ”Create a Lambda function with Python 3.12 runtime” | Discrete, testable |
| ”Define security groups for application layers” | Discrete, testable |
| Bad step titles | Why |
|---|---|
| ”Navigate to the Lambda console and create a Lambda function” | That’s an instruction, not a step title |
Handle wiring
Steps communicate through handles — named references to resources produced by one step and consumed by another. You declare handles as outputs on the producing step and reference them as inputs on the consuming step.
Handle names follow the format {OO}_{SS}_{resource_name}:
OO— zero-padded objective ID (e.g.01)SS— zero-padded step ID within that objective (e.g.02)resource_name— lowercase alphanumeric segments joined by underscores, starting with a letter
Examples: 01_01_vpc_id, 01_02_igw_id, 02_01_function_arn
A step in objective 2 can reference handles from objective 1. Forward dependencies (referencing a handle from a later step) are not allowed.
Worked example
Here’s a two-objective project showing handle flow across objectives:
objectives: - id: "01" title: Create VPC Infrastructure description: | Establish the network foundation by creating a **VPC** with public and private subnets across two availability zones, providing isolation and high availability for your resources. steps: - id: "01" title: Create VPC instructions: [] outputs: - name: 01_01_vpc_id type: AWS::EC2::VPC description: The VPC that will contain all project resources - id: "02" title: Create and attach Internet Gateway instructions: [] inputs: - 01_01_vpc_id outputs: - name: 01_02_igw_id type: AWS::EC2::InternetGateway description: Internet Gateway for public subnet internet access - id: "03" title: Configure public routing instructions: [] inputs: - 01_01_vpc_id - 01_02_igw_id outputs: - name: 01_03_public_rt type: AWS::EC2::RouteTable description: Route table for public subnets
- id: "02" title: Deploy the Application description: | Deploy the application into the VPC, using the network infrastructure created in the previous objective. steps: - id: "01" title: Create the EC2 instance instructions: [] inputs: - 01_01_vpc_id # from objective 01 - 01_03_public_rt # from objective 01 outputs: - name: 02_01_instance_id type: AWS::EC2::Instance description: The application serverNotice that objective 2 references handles from objective 1 directly by name. The test runner resolves these at runtime — your evaluation code receives the actual resource IDs.
What to leave out at this stage
When writing objectives and steps, leave the instructions list empty (instructions: []). Instructions are written in the next phase, after the structure is reviewed and approved. Filling in instructions before the structure is stable means rewriting them every time a step changes.
For more on handle naming rules and how handles flow through the test runner, see Handle wiring.