---
title: Connect to Chronicle
editUrl: https://github.com/Cratis/Chronicle.Go/edit/main/Documentation/connection-strings/index.md
description: Configure endpoints, TLS, OAuth credentials and client ownership in Go.
lastUpdated: 2026-10-06T00:00:00.000Z
---


A client shares one supervised connection generation across stores and namespaces. Create it with `chronicle.NewClient(options...)` for lazy construction, or `chronicle.Dial(ctx, options...)` to complete authentication, structural compatibility and the first kernel keep-alive before returning.

## Connection strings

The default endpoint is `chronicle://localhost:35000`. Supply another endpoint with `chronicle.WithConnectionString`. Credentials use OAuth client credentials, not HTTP Basic:

```text
chronicle://client-id:percent-encoded-secret@kernel.example:35000
chronicle://[::1]:35000
chronicle://localhost:35000?auth=none
```

Percent-encode reserved characters in credentials. Omitted credentials select the public development client `chronicle-dev-client` / `chronicle-dev-secret`; use explicit credentials or `WithTokenSource` in production. `WithNoAuthentication` and `auth=none` suppress SDK token exchange and authorization metadata. Combining authentication modes fails construction.

`ParseConnectionString` performs no I/O. `String()` and Go formatting redact credentials by displaying only scheme and addresses. Errors never echo the input URI. Option names, boolean values and `auth=none` are case-insensitive, like C#. Percent escapes are decoded without changing literal `+` characters into spaces. Unknown query options and duplicate query keys (including case variants) fail instead of becoming ignored configuration.

Use `chronicle://one:35000,two:35000` for multiple endpoints or `chronicle+srv://cluster.example` for `_chronicle._tcp.cluster.example` discovery. SRV records are resolved again on each generation, ordered by priority then descending weight like C#. A connection accepts at most 64 endpoints/records; SRV accepts one seed name and must return a nonempty set. `srvNameServer=127.0.0.1:53` selects a DNS server; `WithSRVResolver` supplies a caller-owned, cancelable resolver.

`loadBalancer=least-connections` is the default. It probes HTTPS `/connections/count` with a two-second budget per endpoint, randomly breaks ties, and makes a best-effort `/connections/reserve` request. Probes run concurrently over the bounded endpoint set after up to 250ms of jitter. Reservations expire server-side after 30 seconds; there is no release endpoint. `loadBalancer=round-robin` starts at a random offset; `loadBalancer=random` independently selects a candidate. Selection happens per generation, not per append. Single endpoints need no probe.

API keys, URI certificate/password options and plaintext `disableTls` still return `ErrUnsupported`. Configure PEM material through `WithTLS`.

## Custom load balancing

Supply `WithLoadBalancer(balancer)` to choose an endpoint with your own
`LoadBalancer.Next(ctx, candidates) (ServerAddress, error)` implementation. Last
option wins. The client borrows the strategy and never closes it, even if it
implements `io.Closer`. A strategy shared by multiple clients must support their
concurrent calls.

This strategy excerpt uses `context` and
`chronicle "github.com/cratis/chronicle.go"` imports:

```go
type preferLastServer struct{}

func (preferLastServer) Next(ctx context.Context, candidates []chronicle.ServerAddress) (chronicle.ServerAddress, error) {
    if err := ctx.Err(); err != nil {
        return chronicle.ServerAddress{}, err
    }
    return candidates[len(candidates)-1], nil
}
```

Pass `chronicle.WithLoadBalancer(preferLastServer{})` to `NewClient` or `Dial`.

Each connection attempt calls `Next` once, including single-address URIs, first
connect and every reconnect. Calls are serialized per client, outside SDK locks.
Candidates are a fresh copy in URI order, or priority/descending-weight order
after SRV re-resolution. You may retain or modify the copy, but must return one
of the original candidates. That address becomes both gRPC endpoint and OAuth
authority. Honor the attempt context: its deadline covers `WithConnectTimeout`
and client close cancels it. Do not call Close/Shutdown from `Next`.

Without an explicit strategy, the URI strategy applies (default
least-connections). Unlike C#'s explicit-over-URI precedence, Go rejects combining
`WithLoadBalancer` with a `loadBalancer=` URI key or `WithGRPCConnection`, whose
owner controls routing. Nil and typed-nil strategies also fail construction with
`ErrInvalidConfiguration`, before I/O.

Errors, contained panics and non-candidate results become `*LoadBalancerError`;
no endpoint is dialed and no OAuth request is made. Its `Error()` and formatting
are fixed diagnostics, while `Unwrap()` preserves ordinary causes for inspection.
Panic values are discarded. First `Connect` returns a non-context selection
failure. A selection error matching a context error is treated like an attempt
timeout: `Connect` keeps waiting on its own context while the supervisor retries.
The supervisor retries subsequent attempts with normal jittered reconnect backoff.
Selection failures are always transient, even when wrapping `Unauthenticated`.

[The executable custom-balancer example](https://github.com/Cratis/Chronicle.Go/blob/main/example_connection_test.go)
shows a strategy choosing the last candidate without network I/O.

## TLS and token sources

Certificate validation is **enabled by default**, unlike the C# development default. `WithDevelopmentDefaults()` explicitly permits a self-signed local kernel certificate. `skipTlsValidation=true` is an explicit URI alternative. Neither overrides an explicit `WithTLS` policy; `skipTlsValidation=false` also wins over development defaults.

`WithTLS(*tls.Config)` clones the configuration and its root pool for both OAuth HTTPS and gRPC. Set `RootCAs` with `x509.NewCertPool`/`AppendCertsFromPEM`; load client certificates with `tls.LoadX509KeyPair`. TLS 1.2 is the minimum. Treat supplied certificates, private keys and TLS callbacks as immutable after configuration. Password-protected certificate files are unsupported; URI certificate options never silently succeed.

`WithTokenSource(source)` accepts a concurrent, context-aware `Token(ctx) (chronicle.Token, error)` implementation. You own that source; the client never closes it. Nonzero expiration must be in the future. Tokens format as redacted, but their `AccessToken` field is sensitive.

Built-in OAuth sends a form POST to the selected kernel's `/connect/token`, caches tokens, refreshes within one minute of expiry and serializes refreshes. A failed refresh can serve a still-valid token; failed exchanges are throttled for five seconds. The default lifetime without `expires_in` is 3600 seconds. Expired credentials, redirects and malformed responses fail closed. Token requests have a five-second budget. An `Unauthenticated` unary or stream failure invalidates the cached token, including its failed-refresh fallback. The next credential acquisition obtains a fresh token; no application operation is automatically retried. Every replacement generation acquires OAuth credentials from its newly selected endpoint. External sources can implement the concurrency-safe, nonblocking `TokenInvalidator.Invalidate()` contract for the same notification.

## Lifecycle and cancellation

- Client, store and sequence handles support concurrent calls. Caller event values must not be mutated during serialization.
- `WithConnectTimeout` defaults to five seconds and never extends a shorter caller deadline.
- Unary operations use the caller's deadline, not a hidden short timeout. Always supply a bounded context for external I/O.
- Concurrent `Connect` calls share startup. Its initiating context bounds that first attempt; other waiters can cancel independently. Later generations use only client-owned lifetimes. Store registrations are single-flight per generation; failed passes remain inspectable and can recover.
- `EventStore(ctx, name, WithNamespace("tenant"))` caches by both store and namespace. The default namespace is `Default`, matching C# and the kernel (case matters). Namespace selection is not authorization.
- `EvictEventStores()` clears all lookup/automatic-registration membership without closing issued handles or resources. Later lookups return new facades; retained handles can still perform explicit operations. See [eviction semantics](/chronicle/go/connection-strings/lifecycle/#evict-cached-event-stores).
- `WithOnConnected` and `WithOnDisconnected` observe protocol readiness/loss asynchronously, not artifact registration. See [connection hook ordering and ownership](/chronicle/go/connection-strings/lifecycle/#connection-hooks).
- `Close()` cancels and joins owned work. `CloseContext(ctx)` bounds that wait; `Shutdown(ctx)` first drains admitted RPCs. A context error means cleanup is incomplete, not successful. Later operations return `ErrClosed`. A custom source that ignores cancellation can delay actual cleanup.
- `WithGRPCConnection(conn)` borrows a channel; you own its security, retry policy and eventual close. The SDK still applies its own admission, metadata and compatibility checks. Multihost/SRV selection on a borrowed channel is rejected because its owner controls routing. You must explicitly select `WithConnectionString` as the OAuth authority, `WithTokenSource`, or `WithNoAuthentication`; the SDK never guesses a localhost authority for a borrowed channel. Use `WithNoAuthentication` if the supplied channel owns credentials. Do not enable application-level append retries on a borrowed channel.

Transient stream loss or missing heartbeats triggers automatic reconnection and registration replay. Existing handles remain usable after recovery. Authentication rejection and incompatible protocols stop supervision until an explicit `Connect`; readiness never silently bypasses those failures. See [connection lifecycle and readiness](/chronicle/go/connection-strings/lifecycle/) for admission, retry and shutdown contracts.

## Diagnose connection failures

Check kernel health with `curl` against `/health`, then inspect the returned error. Use `errors.As` for `CompatibilityError`, `LoadBalancerError` and `EnvelopeError`; use `errors.Is` for `ErrClosed`, `ErrInvalidConfiguration`, `ErrUnsupported` and context cancellation. gRPC status codes remain inspectable with `status.Code` on transport errors.

Compatibility is checked using the embedded canonical descriptor and its independent protocol version, not SDK semver alone. `WithSkipCompatibilityCheck()` or `skipCompatibilityCheck=true` disables preflight at your own risk. Never use it to paper over a missing concurrency capability.
