Docs menuGo (preview)

Docs Integrations

Go (preview)

Record one Go agent turn (a model call, the tool it asks for, the answer) as a Runback run, from your own loop. About ten minutes.

The Go SDK has no client wrapper: you call it at the point in your own loop where each call returns.

Before you begin

  • Go 1.27 or later. Check with go version. Go 1.21 or later fetches 1.27 for you on go get, unless you set GOTOOLCHAIN=local.
  • A Runback account. Create one free.
  • A Runback key. In the app, open Settings › Keys and access › API key, keep Send only selected, select Generate API key and copy it. It is shown once, and starts rb_cloud_.

Steps

  1. Create a module

    Make a folder for the example and start a Go module in it.

    terminalShell
    mkdir weather-agent && cd weather-agent
    go mod init example.com/weather-agent
    Expected output
    go: creating new go.mod: module example.com/weather-agent
  2. Add the Go SDK

    This downloads the SDK from the Go module proxy and adds it to go.mod.

    terminalShell
    go get github.com/letsRunback/runback-go
    Expected output
    go: added github.com/letsRunback/runback-go v0.2.1
  3. Put your key in your shell

    The SDK reads the key from RUNBACK_API_KEY. A key starting rb_cloud_ sends to runback.dev with nothing else set. Replace the placeholder with your key.

    terminalShell
    export RUNBACK_API_KEY="<RUNBACK_API_KEY>"
    Expected output
    Nothing is printed. echo $RUNBACK_API_KEY prints your key, starting rb_cloud_.
  4. Write the agent

    Save this as main.go. Each LLM and Tool call records one step, written where your own code gets the model's reply or the tool's result. Finish records the end of the run and sends everything.

    main.goGo
    // main.go: go get github.com/letsRunback/runback-go, and set RUNBACK_API_KEY.
    package main
    
    import (
    	"fmt"
    	"log"
    
    	"github.com/letsRunback/runback-go/runback"
    )
    
    func main() {
    	run := runback.NewRun(runback.Options{RunName: "weather-agent", Input: "Weather in Paris?"})
    	model := runback.Model{Provider: "openai", ModelID: "gpt-4o-mini"}
    	tools := []runback.ToolDefinition{{Name: "get_weather", Description: "Weather for a city"}}
    	ask := []runback.ModelMessage{{Role: "user", Content: "Weather in Paris?"}}
    
    	// After your model call returns: the model asked for a tool.
    	run.LLM(runback.LlmInput{
    		Model:   model,
    		Request: runback.LlmRequest{Messages: ask, Tools: tools},
    		Response: runback.LlmResponse{
    			FinishReason: runback.Ptr("tool_calls"),
    			ToolCalls:    []runback.ToolCall{{ToolCallID: "call_1", ToolName: "get_weather", Input: map[string]any{"city": "Paris"}}},
    		},
    		Usage:     &runback.TokenUsage{InputTokens: 12, OutputTokens: 5, TotalTokens: 17},
    		LatencyMs: runback.Ptr(640),
    	})
    
    	// After your tool runs.
    	run.Tool(runback.ToolInput{ToolName: "get_weather", ToolCallID: "call_1", Input: map[string]any{"city": "Paris"}, Output: map[string]any{"forecast": "sunny"}})
    
    	// The text turn.
    	run.LLM(runback.LlmInput{
    		Model:     model,
    		Request:   runback.LlmRequest{Messages: ask, Tools: tools},
    		Response:  runback.LlmResponse{Text: runback.Ptr("It is sunny in Paris."), FinishReason: runback.Ptr("stop")},
    		Usage:     &runback.TokenUsage{InputTokens: 20, OutputTokens: 8, TotalTokens: 28},
    		LatencyMs: runback.Ptr(410),
    	})
    
    	res := run.Finish(runback.FinishInput{Output: "It is sunny in Paris."})
    	if !res.OK {
    		log.Fatalf("run not recorded: %v", res.Error)
    	}
    	fmt.Printf("Recorded run %s (%d events)\n", run.RunID, res.Sent)
    }
  5. Run it

    Run it in the same terminal as the key. Your run id will differ.

    terminalShell
    go run .
    Expected output
    Recorded run 01M5B4ZT8JGZC6E6HQXNZS18FP (5 events)
  6. Check the run arrived

    Open Runs in the app. The run at the top is named weather-agent, with the id the last step printed. Open it: two model steps and one get_weather tool step.

What Runback records from this

runback.NewRun in a Go agent loop, scripted Chat Completions model, one tool. 5 events, captured 19 Oct 2026.

  1. Runstart · weather-agent
    {
      "schema_version": 1,
      "run_id": "01M5B446DNZQT0HH0EJDDNH90J",
      "span_id": "01M5B446DNCBQPEY2Z291M2Z07",
      "parent_span_id": null,
      "seq": 0,
      "ts_start": "2026-10-19T22:22:14.965Z",
      "ts_end": "2026-10-19T22:22:14.965Z",
      "type": "run",
      "phase": "start",
      "name": "weather-agent",
      "input": "Weather in Paris? Reply to [redacted:email]",
      "status": "running",
      "error": null,
      "metadata": {}
    }
  2. Modelgpt-4o-mini → get_weather17 tokens · 8 ms · called a tool
    {
      "schema_version": 1,
      "run_id": "01M5B446DNZQT0HH0EJDDNH90J",
      "span_id": "01M5B446DYWFKWJFK1EQNDZ6P0",
      "parent_span_id": "01M5B446DNCBQPEY2Z291M2Z07",
      "seq": 1,
      "ts_start": "2026-10-19T22:22:14.974Z",
      "ts_end": "2026-10-19T22:22:14.974Z",
      "type": "llm",
      "model": {
        "provider": "openai",
        "model_id": "gpt-4o-mini"
      },
      "request": {
        "system": "You are a weather bot.",
        "messages": [
          {
            "role": "user",
            "content": "Weather in Paris? Reply to [redacted:email]"
          }
        ],
        "tools": [
          {
            "name": "get_weather",
            "description": "Weather for a city",
            "parameters": {
              "properties": {
                "city": {
                  "type": "string"
                }
              },
              "required": [
                "city"
              ],
              "type": "object"
            }
          }
        ],
        "params": {}
      },
      "response": {
        "text": null,
        "reasoning": null,
        "finish_reason": "tool_calls",
        "tool_calls": [
          {
            "tool_call_id": "call_1",
            "tool_name": "get_weather",
            "input": {
              "city": "Paris"
            }
          }
        ]
      },
      "usage": {
        "input_tokens": 12,
        "output_tokens": 5,
        "total_tokens": 17
      },
      "latency_ms": 8,
      "error": null
    }
  3. Toolget_weather
    {
      "schema_version": 1,
      "run_id": "01M5B446DNZQT0HH0EJDDNH90J",
      "span_id": "01M5B446DYCAHMGXBNCS06JWMD",
      "parent_span_id": "01M5B446DNCBQPEY2Z291M2Z07",
      "seq": 2,
      "ts_start": "2026-10-19T22:22:14.974Z",
      "ts_end": "2026-10-19T22:22:14.974Z",
      "type": "tool",
      "tool_name": "get_weather",
      "tool_call_id": "call_1",
      "input": {
        "city": "Paris"
      },
      "output": {
        "city": "Paris",
        "forecast": "sunny"
      },
      "latency_ms": 0,
      "error": null
    }
  4. Modelgpt-4o-mini28 tokens · finished
    {
      "schema_version": 1,
      "run_id": "01M5B446DNZQT0HH0EJDDNH90J",
      "span_id": "01M5B446DZZ89N1Q2BHCXFXFT7",
      "parent_span_id": "01M5B446DNCBQPEY2Z291M2Z07",
      "seq": 3,
      "ts_start": "2026-10-19T22:22:14.975Z",
      "ts_end": "2026-10-19T22:22:14.975Z",
      "type": "llm",
      "model": {
        "provider": "openai",
        "model_id": "gpt-4o-mini"
      },
      "request": {
        "system": "You are a weather bot.",
        "messages": [
          {
            "role": "user",
            "content": "Weather in Paris? Reply to [redacted:email]"
          },
          {
            "role": "assistant",
            "content": [
              {
                "input": {
                  "city": "Paris"
                },
                "tool_call_id": "call_1",
                "tool_name": "get_weather"
              }
            ]
          },
          {
            "role": "tool",
            "content": "{\"city\":\"Paris\",\"forecast\":\"sunny\"}"
          }
        ],
        "tools": [
          {
            "name": "get_weather",
            "description": "Weather for a city",
            "parameters": {
              "properties": {
                "city": {
                  "type": "string"
                }
              },
              "required": [
                "city"
              ],
              "type": "object"
            }
          }
        ],
        "params": {}
      },
      "response": {
        "text": "It is sunny in Paris.",
        "reasoning": null,
        "finish_reason": "stop",
        "tool_calls": []
      },
      "usage": {
        "input_tokens": 20,
        "output_tokens": 8,
        "total_tokens": 28
      },
      "latency_ms": 0,
      "error": null
    }
  5. Runend · weather-agentsucceeded
    {
      "schema_version": 1,
      "run_id": "01M5B446DNZQT0HH0EJDDNH90J",
      "span_id": "01M5B446DZFWXFTGSVT3X736E8",
      "parent_span_id": "01M5B446DNCBQPEY2Z291M2Z07",
      "seq": 4,
      "ts_start": "2026-10-19T22:22:14.975Z",
      "ts_end": "2026-10-19T22:22:14.975Z",
      "type": "run",
      "phase": "end",
      "name": "weather-agent",
      "output": "It is sunny in Paris.",
      "status": "success",
      "error": null,
      "metadata": {
        "cassette_digest": "7f2c2310fe67a5fe5e8a66f49d52574a1259388f4833b6804cc3d2a2f060bb2a",
        "cassette_entries": 3,
        "redaction_by_type": {
          "email": 3
        },
        "redaction_count": 3,
        "truncated_fields": 0
      }
    }

The events this integration really sent, redacted in its own process, from a run in Runback's test suite. The model is scripted, so its name and its answers are the test's. The suite re-checks them on every change.

Emails, card numbers, SSNs, private keys and provider API keys are redacted in your process before anything is sent. Redact: runback.RedactStrict adds phone numbers and IPv4 addresses, and runback.RedactOff turns redaction off. The last event carries a fingerprint of the run's model and tool steps, computed when they were recorded. Runback computes the same fingerprint from what it stored, so a changed record shows.

Troubleshooting

Each [runback] line follows the date and time the Go logger prints.

You seeWhat it meansFix
[runback] no RUNBACK_API_KEY set — dropped 5 event(s)The key is not set in the terminal you ran the program from.Export RUNBACK_API_KEY in the same terminal, then run the program again.
[runback] ingest failed 401Runback rejected the key: mistyped, revoked or expired.Copy the whole key again, or generate a new one in Settings › Keys and access › API key.
[runback] ingest failed 422Runback refused the batch. On v0.2.0, an unset Tools or ToolCalls was sent as null.Upgrade: go get github.com/letsRunback/runback-go@latest (v0.2.1 or later).
[runback] RUNBACK_INGEST_URL is not set, so events will be sent to http://localhost:3000. …The key starts rb_live_, an older runback.dev key or a self-hosted one.Generate a new key (it starts rb_cloud_), or set RUNBACK_INGEST_URL to https://runback.dev or your own server.
[runback] ingest error contacting http://localhost:3999: Post "http://localhost:3999/api/ingest": dial tcp [::1]:3999: connect: connection refusedRUNBACK_INGEST_URL points at an address nothing answers on.Unset it for runback.dev, or set it to your self-hosted Runback's origin.
The program printed Recorded run … but the run is not in RunsThe key belongs to a different workspace.Check the workspace name in the app is the one the key came from.

A batch that fails to send stays in memory and is retried on the next send. It is lost if the program exits first, so call Finish before you return.

What it can and cannot do

Go SDK

QuestionAnswerWhy
Code changesOne call per stepNothing is recorded for you: you call LLM and Tool where your own code gets each reply or result.
RedactionIn your processEmails, card numbers, SSNs, private keys and provider API keys. Custom patterns, key allow and deny lists and a custom redactor, which the TypeScript and Python SDKs have, are not in the Go SDK.
Step replayNot yetStep replay and bisection of a Go-recorded run are not available. Its digest still shows whether the record was changed.
LicenceRunback Community LicenceNot an open-source licence.

Next steps

Last updated 2 Nov 2026.