Skip to content

Instantly share code, notes, and snippets.

@CJHwong
Created June 24, 2025 05:54
Show Gist options
  • Save CJHwong/fb14d0fe15621871b3efe78c9ed00064 to your computer and use it in GitHub Desktop.
Save CJHwong/fb14d0fe15621871b3efe78c9ed00064 to your computer and use it in GitHub Desktop.
A Simple Demonstration of building agent with Google ADK
# Absolute or relative path to the local Git repository used by the agent
GIT_AGENT_REPO_PATH=
# AWS credentials for accessing Bedrock services.
# Not required if you're using other providers like Google, Anthropic, or OpenAI.
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION_NAME=

Git Commit Planner Agent

This is a simple demonstration of a multi-agent workflow for planning git commits using Google ADK.
Prompts are structured following the style of OpenAI's GPT-4 Prompting Guide.

What It Does

  • Step 1: Collects unstaged git changes using git diff.
  • Step 2: Iteratively generates and critiques a multi-commit plan until it is clear and actionable.
  • Step 3: Outputs a conventional commit plan for the user to apply.

How It Works

  • InfoGatheringAgent: Runs git diff to get changes.
  • PlannerAgent: Proposes a commit plan.
  • CritiqueAgent: Reviews the plan for completeness and clarity.
  • Loop: Steps 2 and 3 repeat until the plan is approved.
graph TD
    subgraph "Phase 1: Information Gathering" 
        A[Start: User Triggers Agent] --> B(info_gathering_agent);
        B --> C["🛠️ tool: execute_bash<br/>('git diff')"];
    end
    C -- "Provides git_diff_output" --> D;
    subgraph "Phase 2: Iterative Planning (LoopAgent)"
        D(critique_agent_in_loop)
        E(planner_agent_in_loop)
        F{"Is Plan Comprehensive?"}
        D -- "Sends critique of the plan" --> E;
        E -- "Generates/Refines plan" --> F;
        F -- "No, needs refinement" --> D;
        
    end
    F -- "Yes, plan is complete" --> H;
    H[End: Final Commit Plan is Ready];
Loading

Usage

  1. Set GIT_AGENT_REPO_PATH in your environment or use the current directory.
  2. Run:
    python -m git-agent.agent
  3. The agent prints a step-by-step commit plan.

Files

  • agent.py – Main agent workflow.
  • tools.py – Shell command helpers.
  • prompts.yaml – Prompt templates for each agent.

This project is for demonstration and experimentation with agent workflows and prompt engineering.

import yaml
from pathlib import Path
from typing import Any, Dict
from google.adk.agents import LlmAgent, LoopAgent, SequentialAgent
from google.adk.models.lite_llm import LiteLlm
from dotenv import load_dotenv
from . import tools
load_dotenv()
# --- Constants & State Keys ---
# Replace with your model
MODEL = LiteLlm(model="bedrock/anthropic.claude-3-5-haiku-20241022-v1:0")
STATE_GIT_DIFF = "git_diff_output"
STATE_COMMIT_PLAN = "commit_plan"
STATE_CRITICISM = "plan_criticism"
COMPLETION_PHRASE = "The commit plan is comprehensive and actionable."
# --- Prompt Loading ---
def load_prompts() -> Dict[str, Any]:
"""Loads agent instructions from the YAML file."""
prompts_path = Path(__file__).parent / "prompts.yaml"
with open(prompts_path, "r") as f:
return yaml.safe_load(f)
prompts = load_prompts()
# --- Agent Definitions ---
info_gathering_agent = LlmAgent(
model=MODEL,
name="InfoGatheringAgent",
description="Gathers the initial `git diff` output to be used for planning.",
instruction=prompts["info_gathering_agent"]["instruction"],
tools=[tools.execute_bash],
output_key=STATE_GIT_DIFF,
)
critique_agent_in_loop = LlmAgent(
model=MODEL,
name="CritiqueAgent",
description="Reviews the commit plan for completeness and correctness.",
instruction=prompts["critique_agent"]["instruction"].format(completion_phrase=COMPLETION_PHRASE),
output_key=STATE_CRITICISM,
)
planner_agent_in_loop = LlmAgent(
model=MODEL,
name="PlannerAgent",
description="Generates or refines the multi-commit plan based on critique.",
instruction=prompts["planner_agent"]["instruction"].format(completion_phrase=COMPLETION_PHRASE),
tools=[tools.exit_loop],
output_key=STATE_COMMIT_PLAN,
)
planning_loop = LoopAgent(
name="PlanningLoop",
sub_agents=[critique_agent_in_loop, planner_agent_in_loop],
max_iterations=8, # Prevents infinite refinement; adjust as needed for larger diffs.
)
root_agent = SequentialAgent(
name="GitCommitPlannerPipeline",
description=(
"A sequential pipeline that first gathers git diff information, then iteratively generates"
" a detailed, multi-commit plan for the user to execute manually."
),
sub_agents=[info_gathering_agent, planning_loop],
)
if __name__ == "__main__":
# Run the root agent in a test environment.
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.genai import types
APP_NAME = "Git Commit Planner"
USER_ID = "test_user"
SESSION_ID = "test_session"
session_service = InMemorySessionService()
session_service.create_session_sync(
app_name=APP_NAME, user_id=USER_ID, session_id=SESSION_ID
)
runner = Runner(
agent=root_agent, app_name=APP_NAME, session_service=session_service
)
user_content = types.Content(role="user", parts=[types.Part(text="")])
for event in runner.run(user_id=USER_ID, session_id=SESSION_ID, new_message=user_content):
if (
text := event.content.parts[0].text
if event.content and event.content.parts
else None
):
print("AI: \n", text)
info_gathering_agent:
instruction: >
# Role and Objective
You are a persistent utility agent responsible for gathering all unstaged git changes.
# Instructions
- Always use the `execute_bash` tool to run `git diff` and do not guess or fabricate output.
- If you are unsure about the file content or codebase structure, use your tools to gather the relevant information.
- Only terminate your turn when you are certain the required information has been gathered.
# Output Format
- Output only the result of the tool call, with no additional commentary.
critique_agent:
instruction: >
# Role and Objective
You are a meticulous technical reviewer agent tasked with ensuring the commit plan is complete and logical.
# Instructions
- Persist until you have thoroughly reviewed the plan against the provided git diff.
- If you are unsure about any aspect of the plan or diff, use your tools to gather more information—do not guess.
- Review the 'Current Commit Plan' to ensure it is logical, complete, and correctly accounts for all changes in the 'Raw Git Diff'.
- Only terminate your turn when you are certain your review is complete.
# Output Format
- If the plan is incomplete or illogical, provide a concise, one-sentence critique.
- If the plan is comprehensive and accurate, respond *exactly* with "{completion_phrase}" and nothing else.
**Raw Git Diff:**
```
{{git\_diff\_output}}
```
**Current Commit Plan:**
```
{{commit\_plan}}
```
planner_agent:
instruction: >
# Role and Objective
You are an expert git commit planner agent responsible for generating or refining a multi-commit plan based on a git diff and critique.
# Instructions
- Persist and keep iterating until the plan is fully approved by the CritiqueAgent.
- Before each tool call or plan revision, plan your steps and reflect on previous feedback.
- If you are unsure about the diff or plan details, use your tools to gather more information—do not guess.
- Only terminate your turn by calling `exit_loop` when the critique is *exactly* "{completion_phrase}".
# Reasoning Steps
1. Analyze the critique of your last plan.
2. If the critique is *exactly* "{completion_phrase}", call `exit_loop`.
3. Otherwise, generate a new or refined, step-by-step commit plan.
4. Each commit must have a conventional commit message and list the exact lines of code it contains.
# Output Format
- Output ONLY the plan. Do NOT generate any commands to execute.
**Raw Git Diff:**
```
{{git\_diff\_output}}
```
**Critique of Your Last Plan:**
```
{{plan\_criticism}}
```
import subprocess
import shlex
import os
from typing import Any
from google.adk.tools.tool_context import ToolContext
# This will hold the path to the repository we want to manage.
WORKING_DIR = None
def initialize_working_directory():
"""
Sets the working directory from an environment variable.
Must be called once at the start of the agent.
"""
global WORKING_DIR
repo_path = os.environ.get("GIT_AGENT_REPO_PATH")
if repo_path and os.path.isdir(repo_path):
WORKING_DIR = repo_path
print(f"Git agent is now operating in: {WORKING_DIR}")
else:
WORKING_DIR = os.getcwd()
if repo_path:
print(
f"Warning: Path '{repo_path}' not found. Using current directory: {WORKING_DIR}"
)
else:
print(
f"Warning: GIT_AGENT_REPO_PATH not set. Using current directory: {WORKING_DIR}"
)
def execute_bash(command: str) -> str:
"""
Executes a shell command in the repository's directory and returns its output or error.
Args:
command: The raw bash command to execute (e.g., "git status --short").
Returns:
A string containing the stdout from the command, or an error message.
"""
global WORKING_DIR
if WORKING_DIR is None:
initialize_working_directory()
try:
# Using shlex.split is important for security and handling arguments.
result = subprocess.run(
shlex.split(command),
capture_output=True,
text=True,
check=True,
encoding="utf-8",
cwd=WORKING_DIR,
)
return result.stdout.strip()
except FileNotFoundError as e:
return f"Error: Command not found - {e.filename}. Is it installed and in your PATH?"
except subprocess.CalledProcessError as e:
# Return both stdout and stderr for better debugging by the agent.
output = f"Error executing command: {command}\n"
if e.stdout:
output += f"STDOUT:\n{e.stdout.strip()}\n"
if e.stderr:
output += f"STDERR:\n{e.stderr.strip()}\n"
return output
def exit_loop(tool_context: ToolContext) -> dict[str, Any]:
"""
Signals the planning loop to exit when the commit plan is finalized.
Args:
tool_context (ToolContext): The context object provided by the ADK tool framework.
Returns:
dict: An empty dictionary (no output required).
Side Effects:
Sets `tool_context.actions.escalate` to True, which instructs the LoopAgent to terminate.
"""
tool_context.actions.escalate = True
return {}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment