Home / Articles / Practical notes: Gemini for Go Developers: Building Agents in Go

This article is published in English.

Practical notes: Gemini for Go Developers: Building Agents in Go

Operable walkthrough of Practical notes: Gemini for Go Developers: Building Agents in Go: contracts, checks, and drop-in code slots for teams shipping this pattern.

4187 words

This walkthrough rebuilds the path from raw materials to a working system for: Gemini for Go Developers: Building Agents in Go. The focus is operable steps, explicit checks, and code that you can drop into a repo without guessing intent. For the Overview stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.

The anatomy of an agent

When working through the The anatomy of an stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Log request id, model id, and latency on every call. Without that trail, intermittent provider errors look like application bugs.

Agent design: the Retro Game Appraiser

When working through the Agent design the Retro stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Log request id, model id, and latency on every call. Without that trail, intermittent provider errors look like application bugs.

Capabilities and user interaction

When working through the Capabilities and user interaction stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Log request id, model id, and latency on every call. Without that trail, intermittent provider errors look like application bugs. When working through the Capabilities and user interaction stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.

Tool contracts

The Tool contracts stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Pin the interpreter and dependency lockfile before teaching the loop. Drift between laptop and CI is the most common silent break for API demos.

Reasoning strategy

The Reasoning strategy stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Pin the interpreter and dependency lockfile before teaching the loop. Drift between laptop and CI is the most common silent break for API demos.

Implementing the agent with the Go GenAI SDK

The Implementing the agent with stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Pin the interpreter and dependency lockfile before teaching the loop. Drift between laptop and CI is the most common silent break for API demos. The Implementing the agent with stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.

package main

import (
 "bufio"
 "context"
 "fmt"
 "log"
 "os"
 "os/signal"
 "strings"
 "syscall"

 "google.golang.org/genai"
)

// GameItem represents a collectible item in the user's personal inventory.
type GameItem struct {
 Title     string  `json:"title"`
 Platform  string  `json:"platform"`
 Year      int     `json:"year"`
 Condition string  `json:"condition"` // e.g. "Loose Cartridge", "CIB (Complete in Box)", "Mint"
 PricePaid float64 `json:"price_paid"`
 Notes     string  `json:"notes"`
}

// localCatalog simulates an inventory database for retro games.
var localCatalog = []GameItem{
 {
  Title:     "Chrono Trigger",
  Platform:  "Super Nintendo (SNES)",
  Year:      1995,
  Condition: "CIB (Complete in Box)",
  PricePaid: 210.00,
  Notes:     "Includes original map and registration card.",
 },
 {
  Title:     "EarthBound",
  Platform:  "Super Nintendo (SNES)",
  Year:      1994,
  Condition: "Loose Cartridge",
  PricePaid: 180.00,
  Notes:     "Authentic board verified; label in excellent shape.",
 },
 {
  Title:     "Castlevania: Symphony of the Night",
  Platform:  "Sony PlayStation",
  Year:      1997,
  Condition: "CIB (Black Label)",
  PricePaid: 135.00,
  Notes:     "Original soundtrack disc included.",
 },
}

// searchCatalogTool searches the local collection for matching games.
func searchCatalogTool(args map[string]any) map[string]any {
 query, _ := args["query"].(string)
 queryLower := strings.ToLower(strings.TrimSpace(query))

 var matches []GameItem
 for _, item := range localCatalog {
  if strings.Contains(strings.ToLower(item.Title), queryLower) ||
   strings.Contains(strings.ToLower(item.Platform), queryLower) {
   matches = append(matches, item)
  }
 }

 if len(matches) == 0 {
  return map[string]any{
   "found":   false,
   "message": fmt.Sprintf("No items matching %q found in your collection.", query),
  }
 }

 return map[string]any{
  "found":   true,
  "count":   len(matches),
  "results": matches,
 }
}

func main() {
 ctx := context.Background()

 // Initialise GenAI client for Gemini Enterprise
 client, err := genai.NewClient(ctx, &genai.ClientConfig{
  Project:  os.Getenv("GOOGLE_CLOUD_PROJECT"),
  Location: "global",
  Backend:  genai.BackendEnterprise,
 })
 if err != nil {
  log.Fatalf("failed to create client: %v", err)
 }

 // 1. Declare custom function schema for collection lookup
 catalogToolDecl := &genai.FunctionDeclaration{
  Name:        "search_catalog",
  Description: "Search the collector's personal inventory for owned games by title or platform.",
  Parameters: &genai.Schema{
   Type: genai.TypeObject,
   Properties: map[string]*genai.Schema{
    "query": {
     Type:        genai.TypeString,
     Description: "Game title or platform to search (e.g. 'EarthBound', 'SNES').",
    },
   },
   Required: []string{"query"},
  },
 }

 // 2. Configure model tools: custom function declaration + Google Search grounding
 config := &genai.GenerateContentConfig{
  SystemInstruction: &genai.Content{
   Parts: []*genai.Part{
    {Text: "You are an expert Retro Game Appraiser. When evaluating purchases, check the user's " +
     "collection catalog first to see if they already own the item, then check current market " +
     "prices using Google Search to evaluate whether the deal is fair, overpriced, or a bargain."},
   },
  },
  Tools: []*genai.Tool{
   {
    FunctionDeclarations: []*genai.FunctionDeclaration{catalogToolDecl},
   },
   {
    GoogleSearch: &genai.GoogleSearch{},
   },
  },
 }

 // 3. Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM
 sigChan := make(chan os.Signal, 1)
 signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
 go func() {
  <-sigChan
  fmt.Println("\nGoodbye!")
  os.Exit(0)
 }()

 model := "gemini-3.8-flash"
 var contents []*genai.Content

 fmt.Println("Retro Game Appraiser (SDK Agent)")
 fmt.Println("Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit.")
 fmt.Println("-----------------------------------------------------------------")

 scanner := bufio.NewScanner(os.Stdin)
 for {
  fmt.Print("\nUser: ")
  if !scanner.Scan() {
   fmt.Println("\nGoodbye!")
   break
  }

  input := strings.TrimSpace(scanner.Text())
  if input == "" {
   continue
  }
  if strings.EqualFold(input, "exit") {
   fmt.Println("Goodbye!")
   break
  }

  contents = append(contents, &genai.Content{
   Role:  "user",
   Parts: []*genai.Part{genai.NewPartFromText(input)},
  })

  // 4. The Agent Loop: model generation -> tool dispatch -> feedback -> until final answer
  for {
   resp, err := client.Models.GenerateContent(ctx, model, contents, config)
   if err != nil {
    log.Printf("error generating content: %v", err)
    break
   }

   if len(resp.Candidates) == 0 || resp.Candidates[0].Content == nil {
    log.Println("received empty response candidate from model")
    break
   }

   // Append the model's response to the conversation history
   modelContent := resp.Candidates[0].Content
   contents = append(contents, modelContent)

   // Check if the model requested any client-side tool executions
   funcCalls := resp.FunctionCalls()
   if len(funcCalls) == 0 {
    fmt.Printf("\nAppraiser: %s\n", resp.Text())
    break
   }

   // Execute each requested tool and prepare response parts
   var responseParts []*genai.Part
   for _, call := range funcCalls {
    fmt.Printf("[Harness] Executing tool: %s(args=%v)\n", call.Name, call.Args)

    var result map[string]any
    switch call.Name {
    case "search_catalog":
     result = searchCatalogTool(call.Args)
    default:
     result = map[string]any{"error": fmt.Sprintf("unsupported tool: %s", call.Name)}
    }

    responseParts = append(responseParts, genai.NewPartFromFunctionResponse(call.Name, result))
   }

   // Return tool execution results as a user turn
   contents = append(contents, &genai.Content{
    Role:  "user",
    Parts: responseParts,
   })
  }
 }
}
export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
Retro Game Appraiser (SDK Agent)
Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit.
-----------------------------------------------------------------

User: I found a copy of EarthBound for SNES in mint Complete-in-Box (CIB) condition for $350. Do I already own it, and is $350 a good deal compared to current market prices?
[Harness] Executing tool: search_catalog(args=map[query:EarthBound])

Appraiser: Here is your collection check and appraisal for **EarthBound (SNES)**:

1. **Current Collection Status**:
   - You currently own **EarthBound** on Super Nintendo as a **Loose Cartridge**, purchased for **$180.00**.

2. **Market Price Appraisal**:
   - Verified market sales for an authentic, **Complete-in-Box (CIB)** copy of EarthBound typically range between **$1,200.00 and $1,500.00** depending on the condition of the box, tray, and original player's guide.

3. **Recommendation**:
   - At **$350.00**, a genuine Mint CIB copy is an **exceptional deal** (more than 70% below prevailing market value).
   - **Caution**: Because EarthBound is one of the most heavily counterfeited SNES titles, inspect the box printing, registration card, and PCB board carefully before completing the transaction. If verified authentic, this is an outstanding opportunity to upgrade your loose copy to CIB.

User: exit
Goodbye!

Agent development frameworks

For the Agent development frameworks stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Separate client construction from the message loop so providers can be swapped without rewriting the conversation state machine.

Implementing the agent with Genkit

For the Implementing the agent with stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Separate client construction from the message loop so providers can be swapped without rewriting the conversation state machine.

package main

import (
 "context"
 "fmt"
 "log"
 "net/http"
 "os"
 "os/signal"
 "strings"
 "syscall"
 "time"

 "github.com/firebase/genkit/go/ai"
 "github.com/firebase/genkit/go/genkit"
 "github.com/firebase/genkit/go/plugins/googlegenai"
 "google.golang.org/genai"
)

// GameItem represents a collectible item in the user's personal inventory.
type GameItem struct {
 Title     string  `json:"title"`
 Platform  string  `json:"platform"`
 Year      int     `json:"year"`
 Condition string  `json:"condition"`
 PricePaid float64 `json:"price_paid"`
 Notes     string  `json:"notes"`
}

// localCatalog simulates an inventory database for retro games.
var localCatalog = []GameItem{
 {
  Title:     "Chrono Trigger",
  Platform:  "Super Nintendo (SNES)",
  Year:      1995,
  Condition: "CIB (Complete in Box)",
  PricePaid: 210.00,
  Notes:     "Includes original map and registration card.",
 },
 {
  Title:     "EarthBound",
  Platform:  "Super Nintendo (SNES)",
  Year:      1994,
  Condition: "Loose Cartridge",
  PricePaid: 180.00,
  Notes:     "Authentic board verified; label in excellent shape.",
 },
 {
  Title:     "Castlevania: Symphony of the Night",
  Platform:  "Sony PlayStation",
  Year:      1997,
  Condition: "CIB (Black Label)",
  PricePaid: 135.00,
  Notes:     "Original soundtrack disc included.",
 },
}

type CatalogRequest struct {
 Query string `json:"query" jsonschema:"description=The game title or platform to search in the inventory"`
}

type CatalogResponse struct {
 Found   bool       `json:"found"`
 Message string     `json:"message,omitempty"`
 Count   int        `json:"count,omitempty"`
 Results []GameItem `json:"results,omitempty"`
}

type AppraiserRequest struct {
 Prompt string `json:"prompt" jsonschema:"description=The collector's question or purchase offer to evaluate"`
}

type AppraiserResponse struct {
 Appraisal string `json:"appraisal"`
}

func main() {
 ctx := context.Background()

 // 1. Initialise Genkit with Vertex AI plugin
 g := genkit.Init(ctx,
  genkit.WithPlugins(&googlegenai.VertexAI{
   ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"),
   Location:  "global",
  }),
 )

 // 2. Define strongly-typed tool with automatic schema generation
 catalogTool := genkit.DefineTool(
  g,
  "search_catalog",
  "Search the collector's personal inventory for owned games by title or platform.",
  func(ctx *ai.ToolContext, req CatalogRequest) (CatalogResponse, error) {
   queryLower := strings.ToLower(strings.TrimSpace(req.Query))
   queryWords := strings.Fields(queryLower)
   var matches []GameItem

   for _, item := range localCatalog {
    itemText := strings.ToLower(item.Title + " " + item.Platform)
    allMatch := true
    for _, word := range queryWords {
     if !strings.Contains(itemText, word) {
      allMatch = false
      break
     }
    }
    if allMatch {
     matches = append(matches, item)
    }
   }

   if len(matches) == 0 {
    return CatalogResponse{
     Found:   false,
     Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query),
    }, nil
   }

   return CatalogResponse{
    Found:   true,
    Count:   len(matches),
    Results: matches,
   }, nil
  },
 )

 // 3. Define structured appraisal flow with typed request and response
 appraiserFlow := genkit.DefineFlow(
  g,
  "appraise_game",
  func(ctx context.Context, req AppraiserRequest) (AppraiserResponse, error) {
   resp, err := genkit.Generate(ctx, g,
    ai.WithModelName("vertexai/gemini-3.8-flash"),
    ai.WithSystem(
     "You are an expert Retro Game Appraiser. Assist collectors by evaluating prospective purchases, "+
      "cross-referencing their personal inventory, and assessing fair market valuations. "+
      "Always search the collection catalog using search_catalog before providing purchase recommendations.",
    ),
    ai.WithConfig(&genai.GenerateContentConfig{
     ThinkingConfig: &genai.ThinkingConfig{IncludeThoughts: true},
     Tools: []*genai.Tool{
      {
       GoogleSearch: &genai.GoogleSearch{},
      },
     },
    }),
    ai.WithPrompt(req.Prompt),
    ai.WithTools(catalogTool),
   )
   if err != nil {
    return AppraiserResponse{}, fmt.Errorf("appraisal generation failed: %w", err)
   }
   return AppraiserResponse{Appraisal: resp.Text()}, nil
  },
 )

 // 4. Mount flow directly using Genkit's built-in HTTP handler
 mux := http.NewServeMux()
 mux.Handle("POST /api/appraise", genkit.Handler(appraiserFlow))

 port := os.Getenv("PORT")
 if port == "" {
  port = "8080"
 }

 server := &http.Server{
  Addr:    ":" + port,
  Handler: mux,
 }

 // Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM
 serverCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
 defer stop()

 go func() {
  log.Printf("Retro Game Appraiser (Genkit) listening on :%s", port)
  if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
   log.Fatalf("server failed: %v", err)
  }
 }()

 <-serverCtx.Done()
 log.Println("\nShutting down server gracefully...")

 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
 defer cancel()

 if err := server.Shutdown(shutdownCtx); err != nil {
  log.Fatalf("server forced shutdown: %v", err)
 }
 log.Println("Server exited cleanly.")
}

Running the Genkit flow

For the Running the Genkit flow stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Separate client construction from the message loop so providers can be swapped without rewriting the conversation state machine. For the Running the Genkit flow stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.

export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
export PORT=8080
go run main.go
curl -s -X POST http://localhost:8080/api/appraise \
  -H "Content-Type: application/json" \
  -d '{"data": {"prompt": "I found a copy of EarthBound for SNES for $350. Do I own it, and is it a good deal?"}}' | jq .
{
  "appraisal": "### 1. Catalog Check\n**Yes, you already own it.**\n* **Title:** *EarthBound* (SNES, 1994)\n* **Status in Collection:** Loose Cartridge\n* **Condition/Notes:** Authentic board verified; label in excellent shape.\n* **Price Paid:** $180\n\n---\n\n### 2. Market Appraisal & Deal Analysis\n* **Loose Cartridge:** The current going market rate for an authentic loose copy ranges between **$320 and $380**. At **$350**, it is priced right at **fair market value**—neither an overpriced listing nor a significant bargain.\n* **Complete in Box (CIB) / Boxed with Guide:** If this listing happens to include the original big box and strategy guide with scratch-and-sniff cards, $350 would be an extraordinary steal (CIB copies regularly sell for **$1,500–$2,500+**).\n\n---\n\n### 3. Recommendation\n* **Pass (if Loose):** Since you already have an authentic copy in excellent condition, paying retail market price ($350) for a duplicate loose cart does not offer strong value or upside.\n* **Buy immediately (if Complete/Boxed):** Only pull the trigger if it includes the original packaging or represents a major condition upgrade/variant.\n* **Buyer Beware:** If you do ever consider another copy, always inspect the PCB (printed circuit board) screws and chips, as *EarthBound* is one of the most frequently counterfeited games on the SNES."
}

Implementing the agent with Agent Development Kit (ADK)

When working through the Implementing the agent with stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Log request id, model id, and latency on every call. Without that trail, intermittent provider errors look like application bugs.

package main

import (
 "context"
 "fmt"
 "log"
 "os"
 "strings"

 "google.golang.org/genai"

 "google.golang.org/adk/v2/agent"
 "google.golang.org/adk/v2/agent/llmagent"
 "google.golang.org/adk/v2/cmd/launcher"
 "google.golang.org/adk/v2/cmd/launcher/full"
 "google.golang.org/adk/v2/model/gemini"
 "google.golang.org/adk/v2/tool"
 "google.golang.org/adk/v2/tool/functiontool"
 "google.golang.org/adk/v2/tool/geminitool"
)

// GameItem represents a collectible item in the user's personal inventory.
type GameItem struct {
 Title     string  `json:"title"`
 Platform  string  `json:"platform"`
 Year      int     `json:"year"`
 Condition string  `json:"condition"`
 PricePaid float64 `json:"price_paid"`
 Notes     string  `json:"notes"`
}

// localCatalog simulates an inventory database for retro games.
var localCatalog = []GameItem{
 {
  Title:     "Chrono Trigger",
  Platform:  "Super Nintendo (SNES)",
  Year:      1995,
  Condition: "CIB (Complete in Box)",
  PricePaid: 210.00,
  Notes:     "Includes original map and registration card.",
 },
 {
  Title:     "EarthBound",
  Platform:  "Super Nintendo (SNES)",
  Year:      1994,
  Condition: "Loose Cartridge",
  PricePaid: 180.00,
  Notes:     "Authentic board verified; label in excellent shape.",
 },
 {
  Title:     "Castlevania: Symphony of the Night",
  Platform:  "Sony PlayStation",
  Year:      1997,
  Condition: "CIB (Black Label)",
  PricePaid: 135.00,
  Notes:     "Original soundtrack disc included.",
 },
}

type CatalogRequest struct {
 Query string `json:"query" jsonschema:"The game title or platform to search in the inventory."`
}

type CatalogResponse struct {
 Found   bool       `json:"found"`
 Message string     `json:"message,omitempty"`
 Count   int        `json:"count,omitempty"`
 Results []GameItem `json:"results,omitempty"`
}

func main() {
 ctx := context.Background()

 // 1. Initialise Gemini Model adapter for Gemini Enterprise
 model, err := gemini.NewModel(ctx, "gemini-3.8-flash", &genai.ClientConfig{
  Project:  os.Getenv("GOOGLE_CLOUD_PROJECT"),
  Location: "global",
  Backend:  genai.BackendEnterprise,
 })
 if err != nil {
  log.Fatalf("failed to create Gemini model: %v", err)
 }

 // 2. Wrap collection lookup as an ADK Function Tool
 catalogTool, err := functiontool.New(functiontool.Config{
  Name:        "search_catalog",
  Description: "Search the collector's personal inventory for owned games by title or platform.",
 }, func(ctx agent.Context, req CatalogRequest) (CatalogResponse, error) {
  queryLower := strings.ToLower(strings.TrimSpace(req.Query))
  var matches []GameItem

  for _, item := range localCatalog {
   if strings.Contains(strings.ToLower(item.Title), queryLower) ||
    strings.Contains(strings.ToLower(item.Platform), queryLower) {
    matches = append(matches, item)
   }
  }

  if len(matches) == 0 {
   return CatalogResponse{
    Found:   false,
    Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query),
   }, nil
  }

  return CatalogResponse{
   Found:   true,
   Count:   len(matches),
   Results: matches,
  }, nil
 })
 if err != nil {
  log.Fatalf("failed to create catalog tool: %v", err)
 }

 // 3. Define autonomous LLM Agent
 appraiserAgent, err := llmagent.New(llmagent.Config{
  Name:        "retro_game_appraiser",
  Model:       model,
  Description: "Expert appraiser that analyzes retro video game purchases and collection inventory.",
  Instruction: "You are an expert Retro Game Appraiser. Assist collectors by verifying collection " +
   "status with search_catalog, assessing condition variants, and offering objective buying recommendations.",
  Tools: []tool.Tool{
   catalogTool,
   geminitool.GoogleSearch{},
  },
 })
 if err != nil {
  log.Fatalf("failed to create appraiser agent: %v", err)
 }

 // 4. Configure launcher and execute
 config := &launcher.Config{
  AgentLoader: agent.NewSingleLoader(appraiserAgent),
 }

 l := full.NewLauncher()
 if err = l.Execute(ctx, config, os.Args[1:]); err != nil {
  log.Fatalf("run failed: %v\n\n%s", err, l.CommandLineSyntax())
 }
}

Running the ADK agent

When working through the Running the ADK agent stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Log request id, model id, and latency on every call. Without that trail, intermittent provider errors look like application bugs.

export GOOGLE_CLOUD_PROJECT="your-gcp-project-id"
go run main.go
User: Do I have Chrono Trigger in my collection?
Agent: Yes, you have Chrono Trigger in your collection! Here are the details from your inventory:

* Title: Chrono Trigger
* Platform: Super Nintendo (SNES)
* Release Year: 1995
* Condition: CIB (Complete in Box)
* Price Paid: $210.00
* Notes: Includes original map and registration card.

User: What did I pay for it?
Agent: You paid $210.00 for it.
go run main.go web webui api

Agent runtimes

When working through the Agent runtimes stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Log request id, model id, and latency on every call. Without that trail, intermittent provider errors look like application bugs. When working through the Agent runtimes stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.

Cloud Run: the universal backend sweet spot

The Cloud Run the universal stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Pin the interpreter and dependency lockfile before teaching the loop. Drift between laptop and CI is the most common silent break for API demos.

Gemini Enterprise Agent Platform: managed sessions and enterprise RAG

The Gemini Enterprise Agent Platform stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline. Pin the interpreter and dependency lockfile before teaching the loop. Drift between laptop and CI is the most common silent break for API demos.

What’s next?

The What s next stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion. Pin the interpreter and dependency lockfile before teaching the loop. Drift between laptop and CI is the most common silent break for API demos. The What s next stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph.

Operational checklist

The Operational checklist stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope.

Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Pin the interpreter and dependency lockfile before teaching the loop. Drift between laptop and CI is the most common silent break for API demos.

Put human approval on edges that spend money or change production data. Compile-time wiring does not equal business completeness.

Write a short runbook: how to rotate keys, how to drain the queue, how to roll back the last ingest.

Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

Before promoting the stack, freeze versions, capture a golden transcript for the critical path, and confirm rollback steps. Shared environments need rate limits, tenancy checks, and a clear owner for secret rotation. Prefer boring reliability over clever one-off demos.

Batch note for 05c61bdde7aa: keep provider keys out of the repo, set a per-session token ceiling, and store transcripts next to the eval fixtures so later model swaps stay comparable.

For the hardening note 0 stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.

Hardening detail 0/951: measure wall time, error class, and token spend for this note, then decide whether to keep the change based on a fixed question set rather than anecdote.

When working through the hardening note 1 stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments.

Hardening detail 1/951: measure wall time, error class, and token spend for this note, then decide whether to keep the change based on a fixed question set rather than anecdote.