# Go SDK Quickstart Generated: 2026-11-02T06:37:48.708Z Source build: local Canonical docs: https://teammately.ai/docs --- id: sdks.go title: Go SDK Quickstart summary: Consume the Go source module and use context-aware Fieldloop batch and receipt operations. kind: quickstart product_area: developer_tools status: stable updated: 2026-10-31 canonical: /docs/sdks/go --- # Go SDK Quickstart ## Prerequisites and installation Use Go 1.25.1+ and a `teammately-go` source checkout. In an application without an existing workspace: ```sh go work init . /path/to/teammately-go ``` For an existing workspace, use `go work use /path/to/teammately-go`. Keep machine-local workspace configuration out of published module dependencies. The SDK module is `github.com/teammately/teammately-go`. ## Submit and inspect a batch Inject `TEAMMATELY_API_KEY` through the host secret mechanism and configure the three destination identifiers below. This complete program sends one fictional record: ### Example: Context-aware batch submission ```go package main import ( "context" "encoding/json" "fmt" "log" "os" teammately "github.com/teammately/teammately-go" "github.com/teammately/teammately-go/fieldloop" ) func main() { if err := capture(context.Background()); err != nil { log.Fatal(err) } } func capture(ctx context.Context) error { client, err := teammately.New(teammately.Config{ APIKey: os.Getenv("TEAMMATELY_API_KEY"), }) if err != nil { return err } scope := fieldloop.Scope{ Organization: os.Getenv("TEAMMATELY_ORGANIZATION"), Project: os.Getenv("TEAMMATELY_PROJECT"), Source: os.Getenv("TEAMMATELY_FIELDLOOP_SOURCE"), } batch, err := fieldloop.PrepareBatch([]fieldloop.Log{{ SourceEventID: "support-ticket-1042:reply", OccurredAt: "2026-10-31T09:30:00Z", Input: json.RawMessage(`{"question":"Has my bicycle shipped?"}`), Output: json.RawMessage(`{"reply":"Your bicycle is being packed."}`), }}, "support-ticket-1042:batch-1") if err != nil { return err } receipt, err := client.Fieldloop.SubmitPrepared( ctx, scope, batch, teammately.RetryPolicy{}) if err != nil { return err } processed, err := client.Fieldloop.WaitReceipt( ctx, scope, receipt.BatchID, fieldloop.PollOptions{}) if err != nil { return err } fmt.Println(processed.AcceptedCount, processed.DuplicateCount, processed.ConflictCount, processed.RejectedCount) return nil } ``` Pass the application's context and optional deadline in real services. Teammately supplies destination IDs; your application owns event/batch keys. `json.RawMessage` preserves captured JSON numbers. ## Continuous ingestion and evaluations Use `fieldloop.NewIngestor(ctx, client.Fieldloop, scope, options)` for continuous ingestion. `Add` acknowledges memory; `Close` drains. Retain source records until admission is known. [Delivery and Recovery](/docs/sdks/delivery-and-recovery) explains pending batches and processing receipts. `client.Evaluations` provides scoped context, criteria, results, comparisons, external submissions and managed API operations through `evaluations.Scope`. It does not package source or run an evaluator. Use the [CLI](/docs/cli) for local-file workflows. ## Related reference pages {% related-card-grid %} - [Fieldloop API](/docs/public-api/fieldloop) - [Delivery and recovery](/docs/sdks/delivery-and-recovery) - [Fieldloop troubleshooting](/docs/troubleshooting/fieldloop) {% /related-card-grid %} --- id: sdks.overview title: Application SDKs summary: Choose a server-side SDK and follow a complete Fieldloop submission and receipt example. kind: reference product_area: developer_tools status: stable updated: 2026-10-31 canonical: /docs/sdks --- # Application SDKs ## Definition Use an application SDK inside a backend service, worker or serverless function. Supply its API key through the deployed host's secret mechanism. These libraries do not install the CLI, open browser login, discover credentials or run a local evaluator. ## Choose a language | Guide | Package identity | Runtime | Resources | | --- | --- | --- | --- | | [Python](/docs/sdks/python) | `teammately` | Python 3.10+ | Fieldloop | | [JavaScript / TypeScript](/docs/sdks/typescript) | `@teammately/sdk` | Node.js 22+, ES modules | Fieldloop | | [Go](/docs/sdks/go) | `github.com/teammately/teammately-go` | Go 1.25.1+ | Fieldloop and evaluation operations | Each guide describes installation from a source checkout and a batch call. Resource coverage differs across languages. Use [REST](/docs/public-api) or the [CLI](/docs/cli) for evaluation workflows outside your language client's resource surface. ## Fields, states, or lifecycle rules A new Fieldloop Submission key authorizes that capability across its workspace. The application supplies organization, Project and source identifiers; current server permissions apply. Older restricted keys retain their restrictions. `source_event_id` and batch idempotency keys belong to your application. Project/source IDs and receipts are issued by Teammately. In-memory enqueue, durable admission and completed processing are different acknowledgements. Read [Delivery and Recovery](/docs/sdks/delivery-and-recovery) before continuous ingestion. It covers immutable batches, retries, shutdown, row outcomes and privacy. ## Related task pages {% related-card-grid %} - [Python quickstart](/docs/sdks/python) - [TypeScript quickstart](/docs/sdks/typescript) - [Go quickstart](/docs/sdks/go) - [Capture and Inspect Logs](/docs/fieldloop/ingest-logs) {% /related-card-grid %} --- id: sdks.delivery title: SDK Delivery and Recovery summary: Understand batch identity, retries, memory buffering, shutdown and processing outcomes across SDKs. kind: reference product_area: developer_tools status: stable updated: 2026-10-31 canonical: /docs/sdks/delivery-and-recovery --- # SDK Delivery and Recovery ## Definition Your application chooses what to capture, how to redact it, how long to wait and how to retain source records. The SDK preserves captured data and logical batch identity during submission. ## Fields, states, or lifecycle rules | Acknowledgement | What it establishes | | --- | --- | | Ingestor `add` / `Add` returns | A copied record is in memory | | Submission returns a receipt | Durable server admission is known | | Receipt becomes `processed` | Processing finished; inspect all row counts | | Receipt is `failed`, `cancelled` or `deleted` | Terminal outcome to inspect | Buffers are memory-only, with no disk outbox or crash-delivery guarantee. Keep original records until admission is known. ## Immutable batches and retries Prepare a batch once and retain its bytes and application-owned key. Transient `429`, `502`, `503`, `504` and uncertain network failures retry identical bytes/key. `Retry-After` is honored independently of backoff. Permanent failures return directly. Default retry counts and operation durations are uncapped unless the caller supplies limits or cancellation. Choose explicit application policies. A deadline stops the wait, not durable server work. Python `Batch.restore`, TypeScript `Batch.restore` and Go `RestoreBatch` retain original bytes for caller-owned recovery. Keep these bytes secure. Re-encoding changed content under the same key can conflict. ## Continuous ingestion and shutdown Capacities default to uncapped; caller-selected capacities include in-flight rows and can apply backpressure. Automatic dispatch has no imposed delay. Explicit flush and close drain serially. A failed batch remains pending and pauses automatic flushing. Inspect status/pending batch, correct the failure and retry flush or close. Close stops new input and scheduling, consumes an in-flight result and attempts to drain. A failed or timed-out close can be retried. Receipt callbacks report admission, not processing. Return promptly. Python callbacks can run on background threads; callbacks must not close their own ingestor. Go callbacks run outside locks. ## Privacy and errors Select and redact captured content before submission; the SDK does not automatically scrub it. Keep keys on the server. HTTPS is required outside loopback; credentials do not follow redirects. SDKs do not emit telemetry or discover stored login credentials. Inspect structured API errors and processing receipts. A processed batch can have rejected or conflicting rows; an unknown transport outcome is not a confirmed rejection. ## Related task pages {% related-card-grid %} - [Python](/docs/sdks/python) - [TypeScript](/docs/sdks/typescript) - [Go](/docs/sdks/go) - [Fieldloop troubleshooting](/docs/troubleshooting/fieldloop) {% /related-card-grid %} --- id: public-api.benchmark-results title: Retrieve Benchmark Results summary: Read scoped individual Run context, complete rubric results, instructions, and comparable Run differences. kind: api product_area: public_api status: stable updated: 2026-10-31 canonical: /docs/public-api/benchmark-results --- # Retrieve Benchmark Results ## Definition Under the organization/project/Benchmark/Version prefix, current gateway routes include: ```http GET /version-context GET /runs/{runId} GET /runs/{runId}/evaluation-context GET /runs/{runId}/evaluation-results GET /runs/{runId}/evaluation-instructions GET /comparison/{runId}/summary GET /comparison/{runId}/cases ``` External Group/child reads additionally use their returned nested links. ## Read the exact population Verify that the individual Run belongs to the selected Benchmark Version. Read available Case outputs, rubric judgments, native interpretation, rationale and incomplete state. Page through the authoritative filtered result population rather than treating the first page as all results. Instructions and context orient the coding agent; neither is a mutation or a completed evaluation. ## Compare Comparisons preserve individual Run grain and recorded conditions. Missing outputs, profile drift or unavailable native results can prevent a definitive comparison. A later independent Run does not silently replace the reference chosen before outcomes were known. Record the exact starting and candidate Run IDs. Do not interpret legacy compatibility columns as mandatory binary rules when the current Regime supplies native point/gate meaning. ## Read a bounded page ### Example: Read evaluation results ```sh curl --fail-with-body \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ "${BENCHMARK_URL}/runs/${RUN_ID}/evaluation-results?limit=20&offset=0" ``` ```python import os from urllib.parse import quote, urlencode from urllib.request import HTTPRedirectHandler, Request, build_opener class NoRedirect(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None opener = build_opener(NoRedirect()) api_key = os.environ["TEAMMATELY_API_KEY"] url = os.environ["BENCHMARK_URL"] + "/runs/" + quote(os.environ["RUN_ID"], safe="") + "/evaluation-results" url += "?" + urlencode({"limit": "20", "offset": "0"}) headers = {"Authorization": "Bearer " + api_key} request = Request(url, headers=headers, method="GET") with opener.open(request) as response: print(response.read().decode("utf-8")) ``` ```typescript const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.BENCHMARK_URL! + "/runs/" + encodeURIComponent(process.env.RUN_ID!) + "/evaluation-results"); url.searchParams.set("limit", "20"); url.searchParams.set("offset", "0"); const response = await fetch(url, { method: "GET", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); console.log(await response.text()); } ``` ```go package main import ( "context" "fmt" "io" "log" "net/http" "net/url" "os" ) func main() { if err := run(context.Background()); err != nil { log.Fatal(err) } } func run(ctx context.Context) error { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } { target := os.Getenv("BENCHMARK_URL") + "/runs/" + url.PathEscape(os.Getenv("RUN_ID")) + "/evaluation-results" query := url.Values{} query.Set("limit", "20") query.Set("offset", "0") target += "?" + query.Encode() request, err := http.NewRequestWithContext(ctx, "GET", target, nil) if err != nil { return err } request.Header.Set("Authorization", "Bearer " + os.Getenv("TEAMMATELY_API_KEY")) response, err := client.Do(request) if err != nil { return err } payloadBytes, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } fmt.Println(string(payloadBytes)) if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("HTTP %d", response.StatusCode) } } return nil } ``` Use the child Trial's `runId`. The `data` payload holds the page. `limit` accepts 1–100 (default 20); `offset` is nonnegative. Page through the returned population instead of treating one page as all results. | Query | Selection | | --- | --- | | `result` | `passed`, `failed` or `missing` rubric observations | | `case_id`, `policy_id` | One Case or Policy | | `case_rubric_pair_id` | One Case/Rubric pair | | `dimension_id`, `ontology_id` | Declared coverage population | The same filters apply to `evaluation-context`. Unknown/repeated parameters are rejected. Run and instruction reads take no query parameters. ## Compare two Trials ### Example: Compare two Trials ```sh curl --fail-with-body --get \ --header "Authorization: Bearer ${TEAMMATELY_API_KEY}" \ --data-urlencode "target_run_id=${TARGET_RUN_ID}" \ "${BENCHMARK_URL}/comparison/${BASELINE_RUN_ID}/summary" ``` ```python import os from urllib.parse import quote, urlencode from urllib.request import HTTPRedirectHandler, Request, build_opener class NoRedirect(HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): return None opener = build_opener(NoRedirect()) api_key = os.environ["TEAMMATELY_API_KEY"] url = os.environ["BENCHMARK_URL"] + "/comparison/" + quote(os.environ["BASELINE_RUN_ID"], safe="") + "/summary" url += "?" + urlencode({"target_run_id": os.environ["TARGET_RUN_ID"]}) headers = {"Authorization": "Bearer " + api_key} request = Request(url, headers=headers, method="GET") with opener.open(request) as response: print(response.read().decode("utf-8")) ``` ```typescript const apiKey = process.env.TEAMMATELY_API_KEY!; { const url = new URL(process.env.BENCHMARK_URL! + "/comparison/" + encodeURIComponent(process.env.BASELINE_RUN_ID!) + "/summary"); url.searchParams.set("target_run_id", process.env.TARGET_RUN_ID!); const response = await fetch(url, { method: "GET", redirect: "error", headers: { Authorization: `Bearer ${apiKey}`, }, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); console.log(await response.text()); } ``` ```go package main import ( "context" "fmt" "io" "log" "net/http" "net/url" "os" ) func main() { if err := run(context.Background()); err != nil { log.Fatal(err) } } func run(ctx context.Context) error { client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse }, } { target := os.Getenv("BENCHMARK_URL") + "/comparison/" + url.PathEscape(os.Getenv("BASELINE_RUN_ID")) + "/summary" query := url.Values{} query.Set("target_run_id", os.Getenv("TARGET_RUN_ID")) target += "?" + query.Encode() request, err := http.NewRequestWithContext(ctx, "GET", target, nil) if err != nil { return err } request.Header.Set("Authorization", "Bearer " + os.Getenv("TEAMMATELY_API_KEY")) response, err := client.Do(request) if err != nil { return err } payloadBytes, readErr := io.ReadAll(response.Body) response.Body.Close() if readErr != nil { return readErr } fmt.Println(string(payloadBytes)) if response.StatusCode < 200 || response.StatusCode >= 300 { return fmt.Errorf("HTTP %d", response.StatusCode) } } return nil } ``` Use `/cases` for paged changes, with `limit` and `offset`. Both child IDs must belong to the scope. Inspect comparability and incompleteness before interpreting a delta. ## Related reference pages {% related-card-grid title="Related reference pages" %} - [Result Serialization](/docs/public-api/result-schema) - [Submit External Evaluation Outputs](/docs/public-api/evaluation-outputs) - [Evaluation Results](/docs/benchmark-evaluations/results) {% /related-card-grid %}