# How I learned Go in a Day with Antigravity 2.0 and How You Can Do the Same

> Source: <https://cloud.google.com/blog/topics/developers-practitioners/how-i-learned-go-in-a-day-with-antigravity-20-and-how-you-can-do-the-same/>
> Published: 2026-06-15 09:29:00+00:00

I have been exploring how to reclaim my software stack from NPM dependency overhead and replace my resource-intensive Node.js runtime with a compiled, single-binary Go CLI. The result of my efforts is [skl](https://github.com/alexastrum/skl), a fast tool we use for managing Agent Skills, that launches in 2ms and uses only 11MB of memory.

But how exactly did I do it?

Simply, I set the architectural goals and audited the logic, while Antigravity handled the mechanical work of code translation, test generation, and platform path mappings for us. This post describes the step-by-step walkthrough of our migration workflow to help you build yours.

Before writing any code, you start by defining the boundaries of your project. In our case, I wanted a zero-dependency core that used minimal external packages. I decided that our CLI tool needs to be fast, and our security model had to be zero-trust wherever appropriate. In the process, my agent added specific constraints: sanitizing all of our inputs, blocking path traversals, and enforcing depth limits on our folder scans to prevent CPU hangs.

I began by prompting Gemini to audit alternative stacks and help us weigh their tradeoffs.

Here are some alternatives we considered:

Rust was exceptionally performant, but navigating its borrow checker rules and managing its lifetime annotations added too much friction for our simple symlinking tool.

If you choose Python, you will have to distribute a runtime interpreter and manage virtual environments, dragging in packaging overhead via `pip`

that we wanted to avoid.

Zig offered excellent low-level memory controls and compiling speed, but it lacked high-level standard library abstractions for HTTP operations and archive extraction out of the box.

Compiled Swift provided clean scripting on macOS, but its cross-platform compilation capabilities for Windows and Linux were less suited for our multi-platform requirements.

For us, Go struck the right balance: it gave us synchronous, linear code, instant compiling, and a rich standard library.

To ensure I was not doing the same work that someone had already completed before me, I kicked off the project by asking directly:

The agent researched the web and verified that there was no official Go port of the `vercel-labs/skills`

repository. It confirmed that while the official CLI is TypeScript-based and distributed via `npm`

, the Agent Skills specification itself is open and language-agnostic. This meant we were free to build a compiled Go port from scratch.

And since I want to learn in the process, I also asked for Go-specific tips, tricks, and traps:

To make best use of best practices in a language that I'm not familiar with, I decided to find the most popular, well-received Agent Skill (instructions that guide AI coding assistants) and install it before we write any code or even start planning. Grounding the environment first ensures that any code written or planned subsequently conforms to the community's consensus style.

I asked the agent what community agent skills were available for Go:

Once the agent suggested `samber/cc-skills-golang`

, I directed it to install the skill pack:

Once installed, I manually verified that the skill was discovered and ready by typing `/golang-`

to invoke autocompletion.

I initialized the architectural goals by providing the agent with the following instruction:

Our first topic task was the dynamic onboarding flow. When asked what the default should be, I suggested prompting to install `antigravity-cli`

if no agent is found. I also defined the fallback behavior to the `universal`

directory when multiple active agents are detected:

After I approved the Plan, Antigravity handled the systematic conversion of all 51+ agent configuration records (even though I didn't explicitly ask for all this, the AI correctly identified the task as simple enough to just include in the MVP scope), mapping distinct directories for Aider, Claude Code, Cursor, Zed, and others from TypeScript to Go, ensuring we fully covered all environments.

The core structures are conveniently located in one file [types.go](https://github.com/alexastrum/skl/blob/main/src/skl/types.go):

This mapping works well. For example, the detection logic for Zed handles Linux (Flatpak), macOS, and Windows configurations dynamically in just a few lines:

Next, I noticed that the Antigravity user onboarding code was intermingled with the automated mapping. A default like this one is a personal user choice and is better suited for isolation in its own file: [agy-onboarding.go](https://github.com/alexastrum/skl/blob/main/src/skl/agy-onboarding.go):

With version zero scaffolded, it was time to test.

To guarantee that the Go port behaved identically to the original TypeScript CLI, we adopted a Test-Driven Development (TDD) loop. I kicked it off with this prompt:

This initiated the TDD process. Rather than explicitly prompting the agent to use skills, I guided it to fetch the 3rd party best-practice blog post, which reminded the agent about relevant Agent Skills ([golang-how-to](https://github.com/alexastrum/skl/blob/main/.agents/skills/golang-how-to/SKILL.md), [golang-testing](https://github.com/alexastrum/skl/blob/main/.agents/skills/golang-testing/SKILL.md), [golang-error-handling](https://github.com/alexastrum/skl/blob/main/.agents/skills/golang-error-handling/SKILL.md), and [golang-cli](https://github.com/alexastrum/skl/blob/main/.agents/skills/golang-cli/SKILL.md)). Because Antigravity has a sandbox, it parsed these skills and automatically started executing the QA loop. And it will keep re-applying these TDD principles in the current trajectory, anytime it is about to change functional code.

For frontmatter parsing, the agent wrote [frontmatter_test.go](https://github.com/alexastrum/skl/blob/main/src/skl/frontmatter_test.go) first using Go's table-driven test pattern (which was a delightful new pattern for me to discover):

When Antigravity ran `go test`

, it failed cleanly as we expected. My agent then generated [frontmatter.go](https://github.com/alexastrum/skl/blob/main/src/skl/frontmatter.go), implementing a linear string scanning loop that splits the document and unmarshals its YAML metadata. By using simple linear scanning instead of complex regular expressions, we hardened our tool against Regular Expression Denial of Service (ReDoS) vulnerabilities that could crash the application. Including `safety`

as a goal (in my initial prompt) resulted in safer code, even though the original Node implementation was using regular expressions.

Since we're talking about error handling, I'll cover here how we aligned our error structures with Preslav Rachev's [10 Golang Error Handling Commandments](https://preslav.me/2026/05/19/10-golang-error-handling-commandments/). Go requires you to return error values explicitly rather than catching them as exceptions. By integrating these rules, I directed the agent to check its errors immediately at every level (`if err != nil`

) and wrap them with contextual detail (`fmt.Errorf("action: %w", err)`

) before it propagates them up our call stack. While doing a final review of the generated code, I realized Antigravity forgot about this best practice, so I reminded it:

It promptly [fixed](https://github.com/alexastrum/skl/commit/59822bc69464a5fce961231ef56ac0e775855aeb) them across the codebase.

The short answer is **No**.

To ensure that the AI did not introduce subtle bugs or hallucinations during the translation process, I performed code reviews rather than blindly trusting passing test suites.

When I audited the generated tests, I realized that passing green checks alone weren't enough: We were missing tests for that long list of installation locations and the various combinations of having no agents, a single agent, or multiple agents active at the same time. Since this was a complete rewrite, I wanted end-to-end integration coverage for these journeys. To address this gap, I prompted Antigravity with a set of targeted scenarios:

**Note**: Non-parameterized agents like Claude Code or Codex define their configuration paths globally when the package loads (or via environment variables) instead of scanning the active workspace folder at runtime.

The [changelist](https://github.com/alexastrum/skl/commit/02f170e) that added these tests didn't touch any production files, the logic was solid. But I didn't want to leave this to luck. If you care about a specific feature or workflow, you have to be explicit about it. Taking five minutes to verify your end-to-end coverage and defining a few solid tests protects your users from experiencing a broken release down the line.

When you port a full suite of CLI commands (`init`

, `add`

, `list`

, `remove`

, `find`

, `update,...`

) along with their sub-options, you face a large surface area. Rather than migrating them sequentially, it might be better to parallelize our work. In our case, it was a good choice because we wanted each subagent to focus on its specific topic rather than keep in mind the entire tool, and this helped spot a few gaps.

However, subagents are not always the best choice; you should only prioritize parallel execution on voluminous, independent tasks that are clearly bounded. When done right, parallel subagents won't consume significantly more tokens than a single long-running thread, but they protect the main coordinator agent from hitting context compression limits under the weight of a massive codebase. Most simple projects do not require this level of scale. A good rule of thumb is to reserve subagents for workloads equivalent to tens of features with tens of subfeatures.

In previous steps, I ran a single agent to quickly and efficiently build an MVP. But I was not sure whether it fully ported the code. So I asked it directly:

It turned out this was the right call. The subagents conducted an in-depth audit of the commands, catching several option gaps and missing tests that were subsequently integrated in this [audit commit](https://github.com/alexastrum/skl/commit/b9467b6783bbbadbb4236bbde5f49aab7224bd78).

Each subagent worked on exactly one command. They analyzed flag permutations like `-g/--global`

and `--copy`

, drafted table-driven unit tests, and verified their code compiled cleanly. Once they reported back, the main coordinator integrated their changes, resolved any conflicts, and validated that the entire combined project compiled successfully.

To keep our agent focused during this migration, we used the Elephant and Goldfish metaphor, an architectural pattern documented in Google Research's [Elephants, Goldfish, and the New Golden Age of Software Engineering](https://research.google/pubs/elephants-goldfish-and-the-new-golden-age-of-software-engineering). This relies on two distinct roles: the Elephant (the long-term coordinator session holding design rules and codebase memory) and the Goldfish (transient, clean subagents that you spawn to run a single task without background history).

While Antigravity does use automated session compression to manage its context size, you might want to actively manage your context window by maintaining your own checklists and partitioning your work to isolated, transient subagents, when less (context) is more (clarity).

Through some back-and-forth communication, I learned how Go packages are structured and identified the limitations I needed to consider. I now had a cleanly structured and well documented package [main.go](https://github.com/alexastrum/skl/blob/main/main.go) that supported native installation:

I prompted the agent to capture the implementation details and document them for future reference:

To verify the build, auto-run tests, and make sure it works on other machines as well, I asked the agent to:

Antigravity set up the [ci.yml](https://github.com/alexastrum/skl/blob/main/.github/workflows/ci.yml) workflow to run a matrix build, which had a surprising dependency:

Paradoxically, even though we migrated from Node to Go, our GitHub pipeline still depends on Node for standard GitHub Actions helpers like `actions/checkout`

and `actions/setup-go`

.

The tool is completely ready to be run and compiled locally. However, if we want to distribute pre-compiled binaries to other users, we would need to configure code signing for macOS and Windows.

Since building a custom action with code signing is a complex process, it is best reserved for another time.

It was time to document the process itself. To codify this workflow, we [created a reusable Agent Skill](https://github.com/alexastrum/skl/blob/main/.agents/skills/cli-to-go-migration/SKILL.md).

I started by asking the agent to plan a skill creation prompt that included the most important steps:

I got a draft prompt which I iterated upon. After some back-and-forth, I anchored my final instructions on five core rules (though yours might be different). Here's the final prompt I used:

There isn't a one-size-fits-all approach, but asking the agent to create a skill and anchor it on a few guardrails is a good start. In practice, you will likely take turns polishing multiple prompts, until you feel like the agent's responses are aligned with your goals. Then you will ask for a proof read from the AI, and finally perform a human review of the `SKILL.md`

contents.

Rebuilding `skl`

in Go was a fun, educational experience that solved a personal tooling need. It worked, so I decided to document the process. Thinking through this prism, I realized that the journey itself was the reward. You grow as an engineer by codifying your architectural choices into reusable skills and personal experience; while the compiled binary is the physical proof that your process worked.

Surprisingly, the most significant shift I experienced during this migration is behavioral.

Pulling away from an IDE (integrated development environment) and using Antigravity 2.0 made it easier for me to keep a high-level view, preventing me from going in and fixing the issues that arose during the migration. Instead, it guided me to understand why the issues occurred, and learn Go-language specific details.

In a traditional IDE, the moment your assistant encounters an issue, your instinct is to grab your keyboard and debug. Operating without an editor forces you to remain the architect, steering the machine from the navigation deck rather than fighting the engine room fires yourself. That's exactly how we learn to manage agents at scale.
