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 ongo get, unless you setGOTOOLCHAIN=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
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-agentExpected outputgo: creating new go.mod: module example.com/weather-agentAdd 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-goExpected outputgo: added github.com/letsRunback/runback-go v0.2.1Put your key in your shell
The SDK reads the key from
RUNBACK_API_KEY. A key startingrb_cloud_sends to runback.dev with nothing else set. Replace the placeholder with your key.terminalShell export RUNBACK_API_KEY="<RUNBACK_API_KEY>"Expected outputNothing is printed.echo $RUNBACK_API_KEYprints your key, startingrb_cloud_.Write the agent
Save this as
main.go. EachLLMandToolcall records one step, written where your own code gets the model's reply or the tool's result.Finishrecords 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) }Run it
Run it in the same terminal as the key. Your run id will differ.
terminalShell go run .Expected outputRecorded run 01M5B4ZT8JGZC6E6HQXNZS18FP (5 events)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 oneget_weathertool 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.
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": {} }Modelgpt-4o-mini → get_weather
{ "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 }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 }Modelgpt-4o-mini
{ "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 }Runend · weather-agent
{ "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 see | What it means | Fix |
|---|---|---|
[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 401 | Runback 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 422 | Runback 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 refused | RUNBACK_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 Runs | The 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
| Question | Answer | Why |
|---|---|---|
| Code changes | One call per step | Nothing is recorded for you: you call LLM and Tool where your own code gets each reply or result. |
| Redaction | In your process | Emails, 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 replay | Not yet | Step replay and bisection of a Go-recorded run are not available. Its digest still shows whether the record was changed. |
| Licence | Runback Community Licence | Not an open-source licence. |
Next steps
- See what a run contains: Capture.
- Your framework emits OpenTelemetry? The OpenTelemetry quickstart needs no code change.
- Stop a risky tool call before it runs: Control.