Teaching AI Modern Go: Solving the 'Stuck-in-the-Past' Problem with Antigravity

Published: (December 17, 2025 at 04:40 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

The Problem: Outdated Defaults

LeetCode – 1. Two Sum

func TwoSum(nums []int, target int) []int {
    seen := make(map[int]int)
    for i, num := range nums {
        comp := target - num

        if idx, ok := seen[comp]; ok {
            return []int{idx, i}
        }

        seen[num] = i
    }

    return nil
}

Prompting the agent with write benchmark for TwoSum function typically yields a benchmark like this:

func BenchmarkTwoSum(b *testing.B) {
    // Create a larger input for benchmarking
    nums := make([]int, 1000)
    for i := 0; i < 1000; i++ { // Old‑style loop
        nums[i] = i
    }
    target := 1997

    b.ResetTimer()
    for i := 0; i < b.N; i++ { // Old‑style benchmark loop
        TwoSum(nums, target)
    }
}

Issues

  • Uses the classic for i := 0; i < 1000; i++ loop instead of the modern for i := range 1000.
  • Relies on b.N manually rather than the newer b.Loop() method introduced in Go 1.24.

The Solution: Agent Rules

In Antigravity, a Rule is a Markdown file that defines constraints, preferences, and style guides for the agent.
(See the documentation for setup details.)

1. Modernizing Benchmarks (benchmark.md)

// benchmark.md
The `b.Loop` method is now the preferred way to write benchmarks in Go 1.24+.

func BenchmarkExample(b *testing.B) {
    // ... setup ...
    for b.Loop() {
        // ... code to measure ...
    }
    // ... cleanup ...
}

Always use b.Loop() instead of b.N in benchmarks.

2. Modernizing Loops (loops.md)

// loops.md
Each iteration creates a new instance of the variable. There is no need to declare `v := v` inside the loop for closure safety.

for _, v := range data {
    go func() {
        // safe to use v here directly
    }()
}

// Integer ranges are now supported.
for i := range 10 {
    fmt.Println(10 - i)
}

The Result

After adding the rule files and clicking Reload Rules, the agent respects the new conventions. Prompting again:

write benchmark for TwoSum function

produces a plan like:

[NEW] search_test.go
* Create `search_test.go`.
* Implement `BenchmarkTwoSum`.
* Use `b.Loop()` structure.
* Construct a large slice of integers and a target that tests worst/average cases.

Updated Benchmark

func BenchmarkTwoSum(b *testing.B) {
    nums := make([]int, 1000)
    for i := range 1000 {
        nums[i] = i
    }
    target := 1997

    for b.Loop() {
        TwoSum(nums, target)
    }
}

The code now follows the latest Go specifications and is cleaner overall.

Going Further: Enforcing Style Guides

Rules can also enforce broader style guides. For example, you could import the entire Uber Go Style Guide or any custom conventions.

Example: Enforcing Error Wrapping (errors.md)

// errors.md
Do not simply `return err`. Always wrap errors using `github.com/cockroachdb/errors` to provide stack traces and context. Use `Wrap(error, string)` or `Wrapf(error, string, ...interface{})`.

func getUser(id int) error {
    if err := someDatabaseCall(id); err != nil {
        // Wrap the original error with added context
        return errors.Wrapf(err, "some database call id %d", id)
    }
    return nil
}

By defining such rules, you prevent the LLM from falling back to outdated patterns and ensure the generated code aligns with your team’s tools and style. Rules bridge the gap between the LLM’s knowledge and the code you actually want.

Back to Blog

Related posts

Read more »