Skip to content

Latest commit

 

History

History
722 lines (522 loc) · 24 KB

File metadata and controls

722 lines (522 loc) · 24 KB

Agent Starter Pack - AI Coding Agent Guide

Scope: This document is for AI coding agents contributing to the Agent Starter Pack repository itself (the template generator). For guidance on working with generated projects, see llm.txt.

This document provides essential guidance, architectural insights, and best practices for AI coding agents tasked with modifying the Google Cloud Agent Starter Pack. Adhering to these principles is critical for making safe, consistent, and effective changes.


Core Principles for AI Agents

  1. Preserve and Isolate: Your primary objective is surgical precision. Modify only the code segments directly related to the user's request. Preserve all surrounding code, comments, and formatting. Do not rewrite entire files or functions to make a small change.
  2. Follow Conventions: This project relies heavily on established patterns. Before writing new code, analyze the surrounding files to understand and replicate existing conventions for naming, templating logic, and directory structure.
  3. Template-First Mindset: ASP is a template generator. The CLI should remain lean with good defaults. Most features belong in templates, not CLI code.
  4. Search Comprehensively: A single change often requires updates in multiple places. When modifying configuration, variables, or infrastructure, you must search across the entire repository, including:
    • agent_starter_pack/base_templates/ (core templates by language)
    • agent_starter_pack/deployment_targets/ (environment-specific overrides)
    • .github/ and .cloudbuild/ (CI/CD workflows)
    • docs/ (user-facing documentation)

Project Architecture Overview

4-Layer Template System

Template processing follows this hierarchy (later layers override earlier ones):

Layer Directory Purpose
1. Base agent_starter_pack/base_templates/<language>/ Core Jinja scaffolding (Python, Go, more coming)
2. Deployment agent_starter_pack/deployment_targets/ Environment overrides (cloud_run, gke, agent_engine)
3. Frontend agent_starter_pack/frontends/ UI-specific files
4. Agent agent_starter_pack/agents/*/ Agent-specific logic and configurations

Rule: Always place changes in the correct layer. Check if deployment targets need corresponding updates.

Key Directory Structure

agent_starter_pack/
├── agents/                    # Agent-specific files
│   ├── adk/                   # Base ADK agent (Python)
│   ├── adk_a2a/               # A2A-enabled ADK agent
│   ├── adk_go/                # Base ADK agent (Go)
│   ├── adk_live/              # Real-time multimodal agent
│   ├── agentic_rag/           # RAG agent
│   └── langgraph/             # LangGraph-based agent
├── base_templates/            # Core Jinja templates by language
│   ├── python/                # Python project template
│   │   ├── {{cookiecutter.agent_directory}}/
│   │   ├── deployment/
│   │   ├── tests/
│   │   └── Makefile
│   └── go/                    # Go project template
├── deployment_targets/        # Environment-specific overrides
│   ├── agent_engine/          # Agent Engine deployment
│   ├── cloud_run/             # Cloud Run deployment
│   └── gke/                   # GKE Autopilot deployment
├── frontends/                 # UI templates
└── cli/                       # CLI implementation
    ├── commands/              # create, setup-cicd, enhance, etc.
    └── utils/                 # Template processing, helpers

When to Modify What

Change Type Where to Modify Also Check
Affects ALL generated projects base_templates/<language>/ Deployment targets for conflicts
Deployment-specific logic deployment_targets/<target>/ Base templates for shared code
Agent-specific feature agents/<agent>/ Other agents for consistency
New CLI flag/command cli/commands/ cli/utils/ for shared logic
CI/CD changes Both .github/ AND .cloudbuild/ Keep in sync
Documentation docs/ README.md for overview changes

Template Processing Flow

  1. Variable resolution from cookiecutter.json
  2. File copying (base → deployment → frontend → agent overlays)
  3. Jinja2 rendering of file content
  4. File/directory name rendering (Jinja in filenames)

Cross-File Dependencies

Changes often require coordinated updates:

  • Configuration: templateconfig.yamlcookiecutter.json → rendered templates
  • CI/CD: .github/workflows/.cloudbuild/ (must stay in sync)
  • Infrastructure: Base terraform → deployment target overrides

Template Development Workflow

Template changes require a specific workflow because you're modifying Jinja templates, not regular source files.

Note: This workflow applies to both Python and Go templates. Both use Jinja templating with the same patterns ({{cookiecutter.*}}, {% if %}, etc.).

Step-by-Step Process

1. Generate a Test Instance

uv run agent-starter-pack create mytest -p -s -y -d cloud_run --output-dir target

Flags explained:

  • -p / --prototype: Minimal project (no CI/CD or Terraform)
  • -s / --skip-checks: Skip GCP/Vertex AI verification
  • -y / --auto-approve: Skip all confirmation prompts
  • -d: Deployment target
  • --output-dir target: Output to target/ (gitignored)

2. Initialize Git in the Generated Project

cd target/mytest && git init && git add . && git commit -m "Initial"

This creates a baseline for tracking your changes with git diff.

3. Develop with Tight Feedback Loops

  • Make changes directly in target/mytest/
  • Test immediately: make lint, run the code, check output
  • Iterate until the change works correctly
  • Use git diff to see exactly what you changed

4. Backport Changes to Jinja Templates

  • Find the source template: find agent_starter_pack -name "filename.py" -type f
  • Apply your changes to the template file
  • Add Jinja conditionals if the change is conditional
  • Use {%- -%} whitespace control carefully

5. Validate Across Combinations

# Test your target combination
_TEST_AGENT_COMBINATION="adk,cloud_run,--session-type,in_memory" make lint-templated-agents

# Test alternate agent with same deployment
_TEST_AGENT_COMBINATION="adk_live,cloud_run" make lint-templated-agents

# Test same agent with alternate deployment
_TEST_AGENT_COMBINATION="adk,agent_engine" make lint-templated-agents

Why This Workflow?

  • Jinja templates are harder to debug than rendered code
  • Generated projects give immediate feedback on syntax errors
  • Backporting ensures you understand exactly what changed
  • Cross-combination testing catches conditional logic bugs

Language-Specific Notes

The template development workflow above applies to all languages. Here are language-specific details:

Language Agent(s) Linter Test Command Status
Python adk, adk_a2a, adk_live, agentic_rag, langgraph, custom_a2a ruff, ty make lint Production
Go adk_go golangci-lint make lint Production
Java (coming soon) - - In development
TypeScript (coming soon) - - In development

Python example:

uv run agent-starter-pack create mytest -a adk -d cloud_run -p -s -y --output-dir target

Go example:

uv run agent-starter-pack create mytest -a adk_go -d cloud_run -p -s -y --output-dir target

Jinja Templating Rules

Note: These rules apply to both Python and Go templates. Both languages use the same Jinja2 templating patterns.

Templating Engine: Cookiecutter + Jinja2

The starter pack uses Cookiecutter to generate project scaffolding from templates customized with Jinja2. Understanding the rendering process is key to avoiding errors.

Multi-Phase Template Processing:

  1. Cookiecutter Variable Substitution: Replacement of {{cookiecutter.variable_name}} placeholders
  2. Jinja2 Logic Execution: Conditional blocks ({% if %}), loops ({% for %})
  3. File/Directory Name Templating: Jinja2 in filenames is rendered

Block Balancing (Critical)

Every opening Jinja block must have a corresponding closing block.

  • {% if ... %} requires {% endif %}
  • {% for ... %} requires {% endfor %}
  • {% raw %} requires {% endraw %}
{% if cookiecutter.deployment_target == 'cloud_run' %}
  # Cloud Run specific content
{% endif %}

Variable Usage

Distinguish between substitution and logic:

  • Substitution (in file content): {{ cookiecutter.project_name }}
  • Logic (in if/for blocks): {% if cookiecutter.session_type == 'cloud_sql' %}

Whitespace Control

Jinja is sensitive to whitespace. Use hyphens to control newlines:

  • {%- removes whitespace before the block
  • -%} removes whitespace after the block
  • {%- -%} removes whitespace on both sides
{%- if cookiecutter.some_option %}
option = true
{%- endif %}

Conditional Logic Patterns

{%- if cookiecutter.agent_name == "adk_live" %}
# Agent-specific logic
{%- elif cookiecutter.deployment_target == "cloud_run" %}
# Deployment-specific logic
{%- endif %}

Critical Whitespace Control Patterns

Jinja2 whitespace control is the #1 source of linting failures. Understanding these patterns is essential.

Pattern 1: Conditional Imports with Blank Line Separation

Problem: Python requires blank lines to separate third-party imports from project imports. Conditional imports must handle this correctly.

Wrong - Creates extra blank line:

from opentelemetry.sdk.trace import TracerProvider, export
{% if cookiecutter.session_type == "agent_engine" %}
from vertexai import agent_engines
{% endif %}

from app.app_utils.gcs import create_bucket_if_not_exists

Correct - Exactly one blank line:

from opentelemetry.sdk.trace import TracerProvider, export
{% if cookiecutter.session_type == "agent_engine" -%}
from vertexai import agent_engines
{% endif %}

{%- if cookiecutter.is_a2a %}
from {{cookiecutter.agent_directory}}.agent import app as adk_app

{% endif %}
from {{cookiecutter.agent_directory}}.app_utils.gcs import create_bucket_if_not_exists

Key points:

  • Use {%- -%} to control BOTH sides when needed
  • The blank line AFTER the conditional import goes INSIDE the if block when needed
  • Test BOTH when condition is true AND false

Pattern 2: Long Import Lines

Problem: Ruff enforces line length limits. Long import statements must be split.

Wrong - Too long:

from app.app_utils.typing import Feedback, InputChat, Request, dumps, ensure_valid_config

Correct - Split with parentheses:

from app.app_utils.typing import (
    Feedback,
    InputChat,
    Request,
    dumps,
    ensure_valid_config,
)

Pattern 3: File End Newlines

Problem: Ruff requires exactly ONE newline at the end of every file.

Wrong - No newline:

agent_engine = AgentEngineApp(project_id=project_id)
{%- endif %}

Wrong - Extra newline:

agent_engine = AgentEngineApp(project_id=project_id)
{%- endif %}

Correct - Exactly one:

agent_engine = AgentEngineApp(project_id=project_id)
{%- endif %}

Key for nested conditionals:

agent_engine = AgentEngineApp(
    app=adk_app,
    artifact_service_builder=artifact_service_builder,
)
{%- endif -%}
{% else %}

import logging

Notice {%- endif -%} to prevent blank line before the else block.

Whitespace Control Cheat Sheet

# Remove whitespace BEFORE the tag
{%- if condition %}

# Remove whitespace AFTER the tag
{% if condition -%}

# Remove whitespace on BOTH sides
{%- if condition -%}

# Typical pattern for conditional imports
{% if condition -%}
import something
{% endif %}

# Typical pattern for conditional code blocks with blank line before
{%- if condition %}

some_code()
{%- endif %}

# Pattern for preventing blank line between consecutive conditionals
{%- endif -%}
{%- if next_condition %}

Testing Strategy

Testing Coverage Matrix

Critical Principle: Template changes can affect MULTIPLE agent/deployment combinations. Test across combinations when making template modifications.

Dimension Options
Agents adk, adk_a2a, adk_go, adk_live, agentic_rag, langgraph
Deployments cloud_run, gke, agent_engine
Session types in_memory, cloud_sql, agent_engine
Features data_ingestion, frontend_type

Minimum Coverage Before PR

  • Your target combination
  • One alternate agent with same deployment target
  • One alternate deployment target with same agent

Linting Commands

IMPORTANT: Only run linting when explicitly requested by the user. Do not proactively lint unless asked.

# Linting a specific combination
_TEST_AGENT_COMBINATION="agent,target,--param,value" make lint-templated-agents

# Testing a specific combination
_TEST_AGENT_COMBINATION="agent,target,--param,value" make test-templated-agents

Common Test Combinations

# Cloud Run combinations (Python)
_TEST_AGENT_COMBINATION="adk,cloud_run,--session-type,in_memory" make lint-templated-agents
_TEST_AGENT_COMBINATION="adk_live,cloud_run,--session-type,in_memory" make lint-templated-agents

# Agent Engine combinations (Python)
_TEST_AGENT_COMBINATION="adk,agent_engine" make lint-templated-agents
_TEST_AGENT_COMBINATION="adk_live,agent_engine" make lint-templated-agents
_TEST_AGENT_COMBINATION="langgraph,agent_engine" make lint-templated-agents

# GKE combinations (Python)
_TEST_AGENT_COMBINATION="adk,gke,--session-type,in_memory" make lint-templated-agents

# Go template testing
_TEST_AGENT_COMBINATION="adk_go,cloud_run" make lint-templated-agents

# Go template testing (GKE)
_TEST_AGENT_COMBINATION="adk_go,gke" make lint-templated-agents

# With session type variations
_TEST_AGENT_COMBINATION="adk,cloud_run,--session-type,agent_engine" make lint-templated-agents

Testing Workflow for Template Changes

Before committing ANY template change:

# 1. Test the specific combination you're working on
_TEST_AGENT_COMBINATION="adk,cloud_run,--session-type,in_memory" make lint-templated-agents

# 2. Test related combinations (same deployment, different agents)
_TEST_AGENT_COMBINATION="adk_live,cloud_run,--session-type,in_memory" make lint-templated-agents

# 3. Test alternate code paths (different deployment, session types)
_TEST_AGENT_COMBINATION="adk,cloud_run,--session-type,agent_engine" make lint-templated-agents
_TEST_AGENT_COMBINATION="adk,agent_engine" make lint-templated-agents

# 4. If modifying deployment target files, test all agents with that target
# For agent_engine changes:
_TEST_AGENT_COMBINATION="adk,agent_engine" make lint-templated-agents
_TEST_AGENT_COMBINATION="adk_live,agent_engine" make lint-templated-agents
_TEST_AGENT_COMBINATION="langgraph,agent_engine" make lint-templated-agents

Golden Rule: After ANY template change affecting imports, conditionals, or file endings, test AT LEAST 3 combinations:

  1. The target combination
  2. An alternate agent with same deployment
  3. An alternate deployment with same agent

Debugging Linting Failures

Step 1: Identify the Exact Error

# Look for the diff output in the error message
--- app/fast_api_app.py
+++ app/fast_api_app.py
@@ -21,6 +21,7 @@
 from opentelemetry import trace
 from vertexai import agent_engines
+
 from app.app_utils.gcs import create_bucket_if_not_exists

The + line shows what Ruff WANTS to add. In this case, it wants a blank line after agent_engines.

Step 2: Find the Generated File

# Generated files are in target/
cat target/project-name/app/fast_api_app.py | head -30

Step 3: Trace Back to Template

# Find the template source
find agent_starter_pack -name "fast_api_app.py" -type f

Step 4: Check BOTH Branches of Conditionals

  • If {% if condition %} exists, test with condition true AND false
  • Use different agent combinations to toggle different conditions

Common Linting Errors and Fixes

Error Cause Fix
Missing blank line between imports Conditional import without proper spacing Add blank line inside {% if %} block with correct {%- -%} control
Extra blank line between imports Jinja block creating unwanted newline Use {%- endif -%} to strip both sides
Missing newline at end of file Template ends without final newline Ensure template has exactly one blank line at end
Extra blank line at end of file Multiple newlines or {% endif %} creating extra line Use {%- endif -%} pattern
Line too long Import statement exceeds limit Split into multi-line with parentheses

Files Most Prone to Linting Issues

  1. agent_engine_app.py (deployment_targets/agent_engine/)

    • Multiple conditional paths (adk_live, adk_a2a, regular)
    • End-of-file newline issues
  2. fast_api_app.py (deployment_targets/cloud_run/)

    • Conditional imports (session_type, is_a2a)
    • Long import lines
    • Complex nested conditionals
  3. Any file with {% if cookiecutter.agent_name == "..." %}

    • Different agents trigger different code paths
    • Must test multiple agent types

CI/CD Integration

The project maintains parallel CI/CD implementations. Any change to CI/CD logic must be applied to both.

  • GitHub Actions: Configured in .github/workflows/. Uses ${{ vars.VAR_NAME }} for repository variables.
  • Google Cloud Build: Configured in .cloudbuild/. Uses ${_VAR_NAME} for substitution variables.

When adding a new variable or secret, ensure it is configured correctly for both systems in the Terraform scripts that manage them (e.g., github_actions_variable resource and Cloud Build trigger substitutions).


Terraform Best Practices

Unified Service Account (app_sa)

The project uses a single, unified application service account (app_sa) across all deployment targets to simplify IAM management.

  • Do not create target-specific service accounts (e.g., cloud_run_sa)
  • Define roles for this account in app_sa_roles
  • Reference this account consistently in all Terraform and CI/CD files

Resource Referencing

Use consistent and clear naming for Terraform resources. When referencing resources, especially those created conditionally or with for_each, ensure the reference is also correctly keyed.

# Creation
resource "google_service_account" "app_sa" {
  for_each   = local.deploy_project_ids # e.g., {"staging" = "...", "prod" = "..."}
  account_id = "${var.project_name}-app"
  # ...
}

# Correct Reference
# In a Cloud Run module for the staging environment
service_account = google_service_account.app_sa["staging"].email

Pull Request Best Practices

Commit Message Format

<type>: <concise summary in imperative mood>

<detailed explanation of the change>
- Why the change was needed
- What was the root cause
- How the fix addresses it

Types: fix, feat, refactor, docs, test, chore

PR Structure Example

Title: Brief, descriptive summary (50-60 chars)

Fix Cloud Build service account permission for GitHub PAT secret access

Description:

## Summary
- Key change 1 (what was added/modified)
- Key change 2
- Key change 3

## Problem
Clear description of the issue, including:
- Error messages or symptoms
- Why it was failing
- Context about when/where it occurs

## Solution
Explanation of how the changes fix the problem:
- What resources/files were modified
- Why this approach was chosen
- Any dependencies or sequencing requirements

Example (based on actual PR)

Commit:

Fix Cloud Build service account permission for GitHub PAT secret access

Add IAM binding to grant Cloud Build service account the secretAccessor
role for the GitHub PAT secret. This resolves permission errors when
Terraform creates Cloud Build v2 connections in E2E tests.

The CLI setup already grants this permission via gcloud, but the
Terraform configuration was missing this binding, causing failures when
Terraform runs independently.

PR Description:

## Summary
- Grant Cloud Build service account `secretmanager.secretAccessor` role
- Add proper dependency to Cloud Build v2 connection resource

## Problem
E2E tests failed when Terraform attempted to create Cloud Build v2 connections:

Error: could not access secret with service account: generic::permission_denied


The CLI setup grants this permission via gcloud, but Terraform
configuration lacked the IAM binding.

## Solution
Added `google_secret_manager_secret_iam_member` resource to grant the
Cloud Build service account permission to access the GitHub PAT secret
before creating the connection.

Key Principles

  • Concise but complete: Provide enough context for reviewers
  • Problem-first: Explain the "why" before the "what"
  • Professional tone: Avoid mentions of AI tools or assistants

File Modification Checklist

  • Jinja Syntax: All {% if %} and {% for %} blocks correctly closed?
  • Variable Consistency: cookiecutter. variables spelled correctly?
  • Cross-Target Impact: Base template changes checked against deployment targets?
  • CI/CD Parity: Changes applied to both GitHub Actions and Cloud Build?
  • Multi-Agent Testing: Tested with different agent types and configurations?

Quick Reference

Fast Project Creation

# Quick prototype project (no CI/CD, no Terraform, no prompts)
uv run agent-starter-pack create mytest -p -s -y -d agent_engine --output-dir target

# Flags explained:
# -p / --prototype  : Minimal project (no CI/CD or Terraform)
# -s / --skip-checks: Skip GCP/Vertex AI verification
# -y / --auto-approve: Skip all confirmation prompts
# -d : Deployment target
# --output-dir target: Output to target/ (gitignored)

Common Test Combinations

# Agent Engine + prototype (fastest)
uv run agent-starter-pack create test-$(date +%s) -p -s -y -d agent_engine --output-dir target

# Cloud Run with session type
uv run agent-starter-pack create test-$(date +%s) -p -s -y -d cloud_run --session-type in_memory --output-dir target

# GKE with session type
uv run agent-starter-pack create test-$(date +%s) -p -s -y -d gke --session-type in_memory --output-dir target

# Full project with CI/CD
uv run agent-starter-pack create test-$(date +%s) -s -y -d agent_engine --cicd-runner google_cloud_build --output-dir target

Key Files Reference

File Purpose
agent_starter_pack/cli/commands/create.py Main create command, CLI flags, shared options
agent_starter_pack/cli/utils/template.py Template processing, process_template(), CI/CD runner prompt
agent_starter_pack/base_templates/python/pyproject.toml Generated project metadata, [tool.agent-starter-pack] section
agent_starter_pack/base_templates/python/Makefile Generated project Makefile targets

Key Tooling

  • uv for Python: Primary tool for dependency management and CLI execution

Common Pitfalls

  • Hardcoded URLs: Use relative paths for frontend connections
  • Missing Conditionals: Wrap agent-specific code in proper {% if %} blocks
  • Dependency Conflicts: Some agents lack certain extras (e.g., adk_live + lint)
  • makefile_hashes.json: Can cause merge conflicts during active development - regenerate if needed

Project Metadata Structure

Generated projects store creation context in pyproject.toml:

[tool.agent-starter-pack]
# Metadata
name = "my-project"
base_template = "adk"
asp_version = "0.25.0"

[tool.agent-starter-pack.create_params]
# CLI params used during creation - used by enhance command
deployment_target = "cloud_run"
session_type = "in_memory"
cicd_runner = "skip"

The create_params section enables the enhance command to recreate identical scaffolding with the locked ASP version.