Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Alex Mackay 2026
# Golang CI with GitHub Actions
name: Go

on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]

jobs:

unit-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.26.0'

- name: build
run: go build -v ./...

- name: unit-test
run: go test -v ./...

golangci:
runs-on: ubuntu-latest
name: lint
steps:
- uses: actions/checkout@v4

- uses: actions/setup-go@v5
with:
go-version: '1.26.x'
check-latest: true
cache: true

- name: golangci-lint
uses: golangci/golangci-lint-action@v7
with:
install-mode: goinstall
version: latest
args: --timeout=2m
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.out
agent
build/*
docs/generated
data
24 changes: 16 additions & 8 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Alex Mackay 2026


# Build folder
# Build folder (CLI)
BUILD_FOLDER = build
COVERAGE_BUILD_FOLDER ?= $(BUILD_FOLDER)/coverage
UNIT_COVERAGE_OUT ?= $(COVERAGE_BUILD_FOLDER)/ut_cov.out
BIN ?= $(BUILD_FOLDER)/checkout
BIN ?= $(BUILD_FOLDER)/agent-cli

# Packages
PKG ?= github.com/ATMackay/checkout
PKG ?= github.com/ATMackay/agent
CONSTANTS_PKG ?= $(PKG)/constants


Expand All @@ -21,20 +21,28 @@ ifndef DIRTY
DIRTY := $(shell if [ -n "$$(git status --porcelain 2>/dev/null)" ]; then echo true; else echo false; fi)
endif

LDFLAGS := -s -w \
-X '$(CONSTANTS_PKG).Version=$(VERSION_TAG)' \
-X '$(CONSTANTS_PKG).CommitDate=$(COMMIT_DATE)' \
-X '$(CONSTANTS_PKG).GitCommit=$(GIT_COMMIT)' \
-X '$(CONSTANTS_PKG).BuildDate=$(BUILD_DATE)' \
-X '$(CONSTANTS_PKG).Dirty=$(DIRTY)'


build:
@mkdir -p build
@echo ">> building $(BIN) (version=$(VERSION_TAG) commit=$(GIT_COMMIT) dirty=$(DIRTY))"
GO111MODULE=on go build -ldflags "$(LDFLAGS)" -o $(BIN)
@echo "Checkout server successfully built. To run the application execute './$(BIN) run'"
@echo "Agent server successfully built. To run the application execute './$(BIN) run'"

install: build
mv $(BIN) $(GOBIN)

run: build
@./$(BUILD_FOLDER)/agents run --documentation --demo
@./$(BUILD_FOLDER)/agent-cli run documentor --repo https://github.com/ATMackay/agent.git

build/coverage:
test:
@mkdir -p $(COVERAGE_BUILD_FOLDER)
@go test -cover -coverprofile $(UNIT_COVERAGE_OUT) -v ./...

test: build/coverage
@go test -cover -coverprofile $(UNIT_COVERAGE_OUT) -v ./...
.PHONY: build install run test
24 changes: 21 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# AI Agents with Google ADK
# Agent CLI - AI Agents with Google ADK

This is a toy project to build AI agents using a pure Go stack.
This is a toy project to build AI agents using a pure Go stack and explore the capabilities of Google's [ADK](https://google.github.io/adk-docs/get-started/go/).

The aim is to create multiple agents that can be launched from the same CLI
## Getting started

Run the documentation agent on this project


Export API key (Gemini, Claude)
```
export API_KEY=AI...Zs
```

Build agent CLI
```
make build
```

Run the agent ()
```
./build/agent-cli run documentor --repo https://github.com/ATMackay/agent.git --output agentcli-doc.md
```
17 changes: 17 additions & 0 deletions agents/documentor/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package documentor

import "errors"

// Config is the base config struct for documentation agent
type Config struct {
WorkDir string
}

func (c Config) Validate() error {
// ensure workdir is either explicitly set or defaults are set
// Empty workdir not allowed
if c.WorkDir == "" {
return errors.New("empty work dir supplied")
}
return nil
}
64 changes: 38 additions & 26 deletions agents/documentor/documentor.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,56 @@ package documentor

import (
"context"
"log"

"google.golang.org/genai"

"google.golang.org/adk/agent"
"google.golang.org/adk/agent/llmagent"
"google.golang.org/adk/model/gemini"
"google.golang.org/adk/model"
"google.golang.org/adk/tool"
)

type Documentor struct {
// TODO

inner agent.Agent
}

func NewDocumentorAgent(ctx context.Context) (*Documentor, error) {
model, err := gemini.NewModel(ctx, "gemini-2.5-flash", &genai.ClientConfig{})
// NewDocumentor returns a Documentor agent.
func NewDocumentor(ctx context.Context, cfg *Config, model model.LLM) (*Documentor, error) {
// Configure documentor agent tools
fetchRepoTreeTool, err := NewFetchRepoTreeTool(cfg)
if err != nil {
return nil, err
}

readRepoFileTool, err := NewReadRepoFileTool(cfg)
if err != nil {
log.Fatalf("failed to create model: %s", err)
return nil, err
}

// Copied from ADK examples/workflows

// --- 1. Define Sub-Agents for Each Pipeline Stage ---

// Code Writer Agent
// Takes the initial specification (from user query) and writes code.
_, err = llmagent.New(llmagent.Config{
Name: "CodeWriterAgent",
Model: model,
Instruction: `You are a Python Code Generator.
Based *only* on the user's request, write Python code that fulfills the requirement.
Output *only* the complete Python code block, enclosed in triple backticks ('''python ... ''').
Do not add any other text before or after the code block.`,
Description: "Writes initial Python code based on a specification.",
OutputKey: "generated_code", // Stores output in state["generated_code"]
writeOutputTool, err := NewWriteOutputTool(cfg)
if err != nil {
return nil, err
}

// Instantiate Documentor LLM agent
da, err := llmagent.New(llmagent.Config{
Name: "documentor",
Model: model,
Description: "Retrieves code from a GitHub repository and writes high-quality markdown documentation.",
Instruction: buildInstruction(),
Tools: []tool.Tool{
fetchRepoTreeTool, // Fetch Git Repository files
readRepoFileTool, // Read files tool
writeOutputTool, // Write output to file tool
},
OutputKey: StateDocumentation,
})
if err != nil {
log.Fatalf("failed to create codeWriterAgent: %s", err)
return nil, err
}

return &Documentor{}, nil
return &Documentor{inner: da}, nil
}

// Agent returns the inner agent interface (higher abstraction may not be necessary but we will see).
func (d *Documentor) Agent() agent.Agent {
return d.inner
}
31 changes: 31 additions & 0 deletions agents/documentor/prompt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package documentor

func buildInstruction() string {
return `
You are a code documentation agent.

Repository: {repo_url}
Ref: {repo_ref?}
Sub-path filter: {sub_path?}
Output path: {output_path}
Max files to read: {max_files?}

Workflow:
1. Call fetch_repo_tree first using the repository_url, ref, and sub_path from state.
2. Inspect the manifest and identify the most relevant files for architecture and code-level documentation.
3. Prefer entry points, cmd/, internal/, pkg/, config, and core domain files.
4. Skip tests, generated files, vendor, binaries, and irrelevant assets unless they are central.
5. Do not read more than max_files files.
6. Call read_repo_file for each selected file.
7. Write detailed maintainers' documentation in markdown.
8. Call write_output_file with the completed markdown and output_path.

Requirements:
- Explain architecture and package responsibilities.
- Explain key types, functions, interfaces, and control flow.
- Explain configuration, dependencies, and extension points.
- Mention important file paths and symbol names.
- Do not invent behavior beyond the code retrieved.
- If repository coverage is partial, say so explicitly.
`
}
Loading
Loading